/*
	The basic idea behind this driver is that we program a timer to tick at 8Khz (1/8 ms).
	On each tick we check to see which thread is running.  If it is not our thread,
	we cause the current thread to yield execution (sleep for a while) and set our priority
	to the highest.  From window's perspective, the yield function call is run from
	the current thread's context.  In other words, windows thinks that the current
	thread requirest the yield (not our code).  Thus the scheduler will automatically switch
	to our thread when the current one sleeps, because it should be the highest priority
	thread which is available to run.  From the current thread's prospective, the Yield
	function doesn't return until the scheduler runs the current thread again.  So,
	as a result, half of our interrupt handler will not be executed until after the current
	thread is rescheduled.  This means that we can have multiple tail ends or "stubs"
	waiting to run and cleanup the handler and return back to the thread which was
	interrupted in the first place.

	Example:
		Other Thread
		Begin Interrupt
		Yield				---------->	Our Thread
											.
											.
											.
		Finish Interrupt	<----------	(Scheduler)
		Other Thread


	A few complications:  if we kept sleeping all other threads, the system would evetually
	be non-responsive because the only thread that would be able to run is our own, no user
	interfaces, task manager, shutdown thread...  So we must toggle between highest priority
	and a lower priority (currently lowest).  This allows other threads to run every other
	tick.  Another complication is that the yield execution idea only works when we happen
	to interrupt windows when it is in User mode.  If the computer was in Kernel mode,
	we are kind of stuck.

	When in Kernel Mode... what we do is first setup an APC (Asynchronous Procedure Call).
	This tells the kernel to run a snippet of code as soon as possible in user level.  The
	snippet of code is also associated to the offending thread that happened to have spawned
	the kernel request.  When the APC runs it sleeps the thread similiarly to the interrupt
	handler.

	Disk IO...  This is a very big problem.  When doing IO windows is "stuck" in kernel mode
	for potentially several ms and there isn't *that* much we can do about it.  What we can do
	though, is temperalily disable the IDE controller's interrupts.  Meaning, when the current
	IO finishes, window's won't be informed about its completion until the interrupts are
	enabled again.  So this allows us to get some work done between IO operations because the
	scheduler/kernel knows that it hasn't completed its IO, and therefore will drop back
	to user level (running the APC and switching to our thread).

	All things considered, this code is pretty clean, it doesn't rely on *complete* hacks.
	The worst "hack" is the use of undocumented functions exported by the HAL.
	It would be nice if the hardware detection/interfacing was a little cleaner, but
	not enough HAL functions are exported/documented.

	This code was peiced together from many different sources:

		The windows driver interface was copied almost verbatum from:
			Example NT/2000/XP Interrupt & DPC Driver
			Copyright 2001 Craig Peacock, Craig.Peacock@beyondlogic.org
			http://www.beyondlogic.org/interrupts/winnt_isr_dpc.htm

		The interrupt "hooking" (hook.c/h) was taken from and further modified:
			InterruptHook
			Copyright (C) 2003  Alexander M.
			http://www.codeproject.com/system/interrupthook.asp?msg=665029

		The IO/L APIC code was mostly my programming, but a few of the structs
		were taken from FreeBSD and Linux .h files.

		The RTC function definitions came from sample11.c, see rtc.c for details.


	Acronyms used:
		RTC:  Real Time Clock, it is a small timer chip in every x86 system.
				The timer can be configured to tick at a fastest frequency of
				8Khz, and also is where the system time is stored.  This timer
				is used on multiple processor (MP) systems as the system timer, but
				is not normally used on single processor systems (without
				an IOAPIC).  The reason for using this timer instead of the PIT
				or the Local APIC timer, is because the PIT is disabled (what appears
				to be permenantly) in MP systems running windows (and probably single
				processor systems that use the IOAPIC).  The reason not to use the
				Local APIC timer is because I wanted to support as many systems as
				possible and there are still many systems which don't have Local APICs

		PIT:  Programmable Interrupt? Timer, a fairly high frequency timer which almost
				every system has.  a.k.a. 8254 chip

		PIC:  Programmable Interrupt Controller, an anchient architecture which for better
				or worse has stayed around for over 20 years.  It is what handles interrupts
				being generated by hardware.  a.k.a. 8259 chip

		APIC:  Advanced Programmable Interrupt Controller, Intel's "rewrite" of the PIC.
				It is more sensibly designed for modern systems, but most importantly for
				MP systems.  There are two devices often called APIC, the Local APIC
				and the IO APIC.  The Local APIC is built in to all intel processors
				newer than original Pentium (maybe a few Pentium IIs).  So on an MP
				system each processor has its own Local APIC to interface with.
				The IO APIC is an extension to the Local APIC, and it allows for
				more hardware interrupts than the normal 16 IRQs (0-15).

		MP:  Multiple Processor

		APC:  Asynchronous Procedure Call, A way of running code at user level when we
				are stuck in the kernel.  It will be run once the kernel switches to user
				level.

		HAL:  Hardware Abstraction Layer, used by windows as a common interface to
				different hardware.  Unfortunately, not too many useful functions are
				exported, so we have to still do a lot of hardware auto-detection ourself.

		CS:  Code Segment, x86 memory was originally organized into 64K segments.  So
				to access more than 64K a segment register could be used to specify
				which segment to access.  With the advent of virtual memory, segments
				are now "technically" 2^32 in size (as in all 4G of addressable memory).
				The main use of segments with virtual memory is to have different address
				spaces.  So for kernel code, CS is set to 0x0008, and user code seems to
				run in segment 0x001B.  Also, the lower 2 bytes of the segment value
				is used to determine privileges.  Specifically, the lower 2 bytes of CS
				determines CPL (Code Privilege Level).

		BSOD:  Blue Screen Of Death, I think this one is self explanatory.

		EOI:  End Of Interrupt, a signal to the interrupt controller that we are done with
				the current interrupt and we can now accept more interrupts.

	Vocabulary
		Interrupt Handler:  This is the the "function" that gets called when an interrupt
				occurs.  It can be run at almost any time, and interrupt almost any running
				code.  Therefore, the handler must store/restore all of the registers
				(pushad/popad).  Also, kernel mode is associated to CS = 0x0008 (also
				where our interrupt handler is located), so when we call kernel level
				functions they can/do detect that they were called from CS = 0x0008,
				and they assume that some of the segments are set to specific values.
				Specifically fs, gs, ds and es must be set to the correct values. So
				the handler must store/set/restore these segments.

		Vector:  there are actually two slightly different meanings to this.  First,
				it can mean the interrupt #(UCHAR) which is triggered by hardware/software.
				Second, it can mean the data structure that points to the interrupt
				handler.  Based upon the datatype used, the real meaning should be
				obvious.

		Stub:  a name given to the second half of the interrupt handler which may not
				complete until the interrupted thread gets scheduled again.
*/

#include <ntddk.h>
#include "RTInterrupt.h"
#include "hook.h"
#include "apic.h"
#include "rtc.h"
#include "ports.h"
#include "int.h"

// The names used to access our driver
WCHAR NameBuffer[] = L"\\Device\\RTInterrupt";
WCHAR DOSNameBuffer[] = L"\\DosDevices\\RTInterrupt";

DWORD ticks = 0;  // used to keep track of when we need to trigger the old interrupt
DWORD counter = 0; // counts how many ticks have occured.
DWORD timerTicks = 0; // how many ticks between calls to the old interrupt
DWORD missedTicks = 0; // track how many ticks we are stuck in the kernel
DWORD stubCount = 0; // how many stubs are currently waiting to finish
DWORD cleaningUp = 0; // a flag to indicate that the driver is uninstalling, the stubs should exit quickly and not reprogram the hardware
DWORD useOldInterrupt = 0; // a flag to indicate if we should trigger the old interrupt

HANDLE ThreadID;  // identifier to recognize our thread by
PKTHREAD pKThread;
HANDLE ProcessID;
PCLIENT_ID IDs;
UCHAR mem[sizeof(CLIENT_ID)+10];

OBJECT_ATTRIBUTES ObjectAttr;


UCHAR RTC_Vector; // the interrupt # for the RTC (# associated to irq8)
UCHAR PrimaryHD_Vector; // the interrupt # for the primary IDE controller
UCHAR SecondaryHD_Vector; // the interrupt # for the secondary IDE controller
UCHAR OldInterrupt_Vector; // the interrupt # to copy the RTC vector to
UCHAR LowPriority; // normally 1
UCHAR HighPriority; // normally 31

DWORD ret;
DWORD interruptedCS;

UCHAR oldTimerSettingsA, oldTimerSettingsB; // variables to store the RTC settings

DWORD HardDiskDisabled = 0; // a flag to indicate when the IDE controller's interrupt(s) are disabled

KPRIORITY priority;  // a variable to store the thread priority into (31, 1).

#define NRYIELDAPS 10
KAPC YieldAPCs[NRYIELDAPS]; // structure to store the APC info
PKAPC pYieldAPC;

INT_VECTOR RTC_Vector_backup; // variables to store backups of the interrupt vectors
INT_VECTOR OldInterrupt_Vector_backup;

// Several partially documented functions exported by the kernel (but not defined)
NTSTATUS WINAPI ZwYieldExecution(VOID); // we may want to modify the Thread Priority directly instead of using this call, but that would be more of a hack and OS dependent

VOID WINAPI KeInitializeApc(PKAPC Apc, PKTHREAD Thread, UCHAR StateIndex,
	PKKERNEL_ROUTINE KernelRoutine, PKRUNDOWN_ROUTINE RundownRoutine,
	PKNORMAL_ROUTINE NormalRoutine OPTIONAL, KPROCESSOR_MODE ApcMode, PVOID NormalContext);

BOOLEAN WINAPI KeInsertQueueApc(PKAPC Apc, PVOID SysArg1, PVOID SysArg2, KPRIORITY PriorityBoost);

// EXTREEMLY undocumented functions exported by the HAL
// WARNING THESE DEFINITIONS MAY NOT WORK UNDER DIFFERENT OS VERSIONS
// In my system the second parameter is not used, and thus I don't know what to call it.
VOID WINAPI HalEnableSystemInterrupt(DWORD vector, DWORD unknown, DWORD nEdge_LevelTriggered);
VOID WINAPI HalDisableSystemInterrupt(DWORD vector, DWORD unknown);

// This function schedules the current RealTime thread to run
void SwitchToThread()
{
	if (missedTicks > 5) KdPrint( ("RTInterrupt.sys: count %d missed %d 0x%08X 0x%08X\n", counter, missedTicks, PsGetCurrentProcessId(), ProcessID) );

	// allow another thread to run every 8th cycle
	if ((counter & 0x7) < 7 || missedTicks > 0)
	{
		priority = HighPriority; //31 is highest

		if (PrimaryHD_Vector) HalDisableSystemInterrupt(PrimaryHD_Vector, 0);
		if (SecondaryHD_Vector) HalDisableSystemInterrupt(SecondaryHD_Vector, 0);
		HardDiskDisabled = 1;
	} else {
		priority = LowPriority; //5 is lowest priority

		HardDiskDisabled = 0;
		if (PrimaryHD_Vector) HalEnableSystemInterrupt(PrimaryHD_Vector, 0, 0);
		if (SecondaryHD_Vector) HalEnableSystemInterrupt(SecondaryHD_Vector, 0, 0);
	}

	counter++;
	missedTicks = 0;

	KeSetPriorityThread(pKThread, priority);
}

PKAPC getYieldAPC()
{
	for (int i=0;i<NRYIELDAPCS;i++)
	{
		if (YieldAPCs[i].Thread == NULL)
		{
			return &(YieldAPCs[i]);
		}
	}

	return NULL;
}

// When IO is done we need a special away of yeilding these threads as quickly as possible
void YieldIO(PKAPC Apc, PKNORMAL_ROUTINE norm_routine, PVOID context, PVOID SysArg1, PVOID SysArg2)
{
	Apc->Thread = NULL;
}

/* Basic idea of this handler is to interrupt windows every 1/8 ms, it checks to see if the
   our thread of interest is currently running, if it isn't it will Yield the current thread.
   When the current thread Yeilds, it also means that any code following the Yield will not
   be run until the Yield returns (when the scheduler runs the thread again after the Yield).
   Because the interrupt finalization doesn't get run until after the Yield statement, we can
   actually have multiple copies of the interrupt handler in memory waiting to finalize.
   These waiting handlers I call stubs, and we need to keep track of how many there are so
   that we can wait for them to finish before freeing memory for our driver. (That is unless
   you like BSODs...)

   Don't declare any local variables in this function, because we mangle the stack.
*/
__declspec( naked ) void RTC_Handler( void )
{
	//when the interrupt starts, interrupts are already disabled
	// save state, so that we won't mess up the thread that we have interrupted
	__asm {
		//These registers are automatically pushed onto the stack when the interrupt occurs:
		// SS(+16), ESP(+12), EFlags(+8), CS(+4), EIP(+0)
		pushad // eax, ecx, edx, ebx, esp, ebp, esi, edi

		// windows detects our code as being part of the kernel (because or code runs on CS == 0x8)
		// since it detects that, it assumes that these segments are set to specific values, but we
		// need to store the old values first
		push	fs
		push	ds
		push	es
		push	gs

		mov     ebx,0x30
		mov     eax,0x23
		mov     fs,bx
		mov     ds,ax
		mov     es,ax
	}

	if (counter == 0) KdPrint( ("RTInterrupt.sys: Interrupt Handler run for first time.\n") );

	ticks--;
	stubCount++;

	// if we aren't going to call the windows interrupt handler, we need to acknowledge
	// the interrupt ourselves
	if (!useOldInterrupt || (ticks != 0))
	{
		// we must reset the hardware so that it can trigger another interrupt
		// because we need to have a reenterent interrupt handler

		// this seems to be required to get the interrupt to continue
		read_rtc_register(0x0C);

		// send End Of Interrupt signal to the APIC
		apic_send_eoi();

		// send EOI to the PIC
		outportb(0xA0, 0x20);
		outportb(0x20, 0x20);
	}

	// as long as we are at PASSIVE_LEVEL or less we can call Yield...
	// else install an Apc on the current thread, so that when it completes it will Yield
	if (KeGetCurrentIrql() <= PASSIVE_LEVEL)
	{
		SwitchToThread();
	} else {
		if (PsGetCurrentThreadId() != ThreadID)
		{
			missedTicks++;

			// doesn't try to toggle thread priority here because it does sometimes? call SwitchContext
			// thus it might cause a BSOD

			if ((pYieldAPC = getYieldAPC()) == NULL)
			{
				// install the Apc to as soon as possible, yield the current thread
				KeInitializeApc(pYieldAPC, KeGetCurrentThread(), 0, (PKKERNEL_ROUTINE)&YieldIO, 0, NULL, 0, NULL);
				KeInsertQueueApc(pYieldAPC, NULL, NULL, 0);
			}
		}
	}

	if (useOldInterrupt && (ticks == 0))
	{
		ticks = timerTicks;

		// we will almost certainly lose control here
		interrupt(OldInterrupt_Vector); // call the copied interrupt handler
	}

	// if the timer has been reprogrammed by windows while we are running
	// we need to detect what the new value is and maybe change our timerTicks value
	if (!cleaningUp && (read_rtc_register(0x0A) &0x0F) != 0x03)
	{
		KdPrint( ("RTInterrupt.sys: Reprogramming RTC\n") );

		// since windows almost certainly reprogrammed this, we need to capture the new state
		oldTimerSettingsA = read_rtc_register(0x0A);
		timerTicks = 1 << ((oldTimerSettingsA &0x0F) - 0x03);

		write_rtc_register(0x0A,(oldTimerSettingsA &0xF0) | 0x03);  // set the clock to 8Khz
	}

	stubCount--;

	// clean up the interrupt handler, and return to whatever we interrupted
	__asm {
		pop	gs
		pop es
		pop ds
		pop fs
		popad
		iretd
	}
}

NTSTATUS InterruptCreateDispatch(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
{
	Irp->IoStatus.Information = 0;
	Irp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}

VOID InterruptUnload(IN PDRIVER_OBJECT DriverObject)
{
	UCHAR temp;
	UNICODE_STRING uniDOSString;
	LARGE_INTEGER  CurrentTime;
	LARGE_INTEGER  StartTime;
	DWORD i;
	DWORD j = 3;
	ULONG tmp;

	KdPrint( ("RTInterrupt.sys: Interrupt.sys is Unloading\n") );

	__asm cli;

	cleaningUp = 1;

	// reset the RTC to the original value
	write_rtc_register(0x0A, oldTimerSettingsA);
	write_rtc_register(0x0B, oldTimerSettingsB);

	__asm sti;

	// there could be a pending interrupt, it will happen here...

	KdPrint( ("RTInterrupt.sys: Interrupt.sys is Unloading...\n") );

	__asm cli;

	RestoreInterrupt(RTC_Vector,&RTC_Vector_backup);
	if (OldInterrupt_Vector) RestoreInterrupt(OldInterrupt_Vector,&OldInterrupt_Vector_backup);

	__asm sti;

	KdPrint( ("RTInterrupt.sys: Interrupt.sys is Unloading......\n") );

	KeSetPriorityThread(pKThread, 1);

	while (stubCount != 0)
	{
		KdPrint( ("RTInterrupt.sys: Waiting for %d stub(s) to complete\n", stubCount) );

		ZwYieldExecution(); // give up control so that the stubs can complete
	}

	if (HardDiskDisabled)
	{
		if (PrimaryHD_Vector) HalEnableSystemInterrupt(PrimaryHD_Vector, 0, 0); // make sure hard disk is re-enabled
		if (SecondaryHD_Vector) HalEnableSystemInterrupt(SecondaryHD_Vector, 0, 0); // make sure hard disk is re-enabled
	}

	apic_cleanup();

	/* Delete Symbolic Link */
	RtlInitUnicodeString(&uniDOSString, DOSNameBuffer);
	IoDeleteSymbolicLink (&uniDOSString);

      /* Delete Device */
	IoDeleteDevice(DriverObject->DeviceObject);

	ExSetTimerResolution(10,FALSE); // reverse windows back to normal timer tick rate
}


NTSTATUS InterruptDeviceControl(IN PDEVICE_OBJECT DeviceObject, IN PIRP pIrp)
{
	PIO_STACK_LOCATION  irpSp;
	NTSTATUS            ntStatus = STATUS_SUCCESS;

	DWORD               inBufLength;   /* Input buffer length */
	DWORD               outBufLength;  /* Output buffer length */

	PRTsettings			settings;
    PVOID               ioBuffer;
   	KIRQL				Irql;
   	KAFFINITY			Affinity;
	ULONG tmp;

    irpSp = IoGetCurrentIrpStackLocation( pIrp );
    inBufLength = irpSp->Parameters.DeviceIoControl.InputBufferLength;
    outBufLength = irpSp->Parameters.DeviceIoControl.OutputBufferLength;

    ioBuffer = pIrp->AssociatedIrp.SystemBuffer;

	settings = (PRTsettings)ioBuffer;

	switch ( irpSp->Parameters.DeviceIoControl.IoControlCode )
	{
		case IOCTL_GET_SETTINGS:
			if (outBufLength >= sizeof(RTsettings))
			{
				// need a better way of getting the HD controller IRQs (if we really want to support this...)
				if (setup_apic())
				{
					RTC_Vector = ioapic_get_vector(8); //irq8
					PrimaryHD_Vector = ioapic_get_vector(14); //irq14, primary IDE controller (normally)
					SecondaryHD_Vector = ioapic_get_vector(15); //irq15, secondary IDE controller (normally)
				} else {
					// use the Hal to tell us what the irq vectors are
					RTC_Vector = (UCHAR)HalGetInterruptVector(Isa,
														0,
														8, //irq8
														8,
														&Irql,
														&Affinity);
					PrimaryHD_Vector = (UCHAR)HalGetInterruptVector(Isa,
														0,
														14, //irq14
														14,
														&Irql,
														&Affinity);
					SecondaryHD_Vector = (UCHAR)HalGetInterruptVector(Isa,
														0,
														15, //irq15
														15,
														&Irql,
														&Affinity);
				}

				// if the RTC is already programmed to generate interrupts, then assume that we will need
				// to call the interrupt handler being used currently
				__asm cli
				useOldInterrupt = (read_rtc_register(0x0B) & 0x40) != 0;
				__asm sti

				settings->useOldInterrupt = (UCHAR)useOldInterrupt;  // signal to use the copied interrupt handler
				settings->RTC_Vector = RTC_Vector;
				settings->PrimaryHD_Vector = PrimaryHD_Vector;
				settings->SecondaryHD_Vector = SecondaryHD_Vector;

				// should use some mechanism to detect a free IRQ
				settings->OldInterrupt_Vector = 0xFE; // the vector to be used to copy the old interrupt handler into, this "should" be unused on every system
				settings->LowPriority = 7; //LOW_PRIORITY; // 0 is invalid
				settings->HighPriority = HIGH_PRIORITY;

				ThreadID = PsGetCurrentThreadId();
				pKThread = KeGetCurrentThread();
				ProcessID = PsGetCurrentProcessId();

				pIrp->IoStatus.Information = sizeof(RTsettings); // Output Buffer Size

				if (RTC_Vector == 0) DbgPrint("RTInterrupt.sys: HalGetInterruptVector failed\n");

				KdPrint( ("RTInterrupt.sys: useOldInterrupt %d\n", useOldInterrupt) );
			} else {
				KdPrint( ("RTInterrupt.sys: Not enough output space.\n") );
				ntStatus = STATUS_UNSUCCESSFUL;
				pIrp->IoStatus.Information = 0;
			}
            break;
	  case IOCTL_START:
			if (inBufLength >= sizeof(RTsettings))
			{
				useOldInterrupt = settings->useOldInterrupt;
				RTC_Vector = settings->RTC_Vector;
				PrimaryHD_Vector = settings->PrimaryHD_Vector; // set these to 0 to disable the IDE controller disabling
				SecondaryHD_Vector = settings->SecondaryHD_Vector;
				OldInterrupt_Vector = settings->OldInterrupt_Vector; // the vector to copy the current RTC_Vector into, so that we can still trigger it
				LowPriority = settings->LowPriority;
				HighPriority = settings->HighPriority;

				ExSetTimerResolution(10000,TRUE); // put windows into 1ms timer tick mode

				__asm cli; //disable all interrupts

				// if the RTC_Vector is mapped into a space higher than normal and the timer doesn't seem to be set, then reprogram the PIC
				if (useOldInterrupt)
				{
					// so we need to make a new interrupt so that we can still trigger the system interrupt
					BackupInterrupt(OldInterrupt_Vector, &OldInterrupt_Vector_backup);
					CopyInterrupt(RTC_Vector, OldInterrupt_Vector);
				} else {
					OldInterrupt_Vector = 0;
					write_rtc_register(0x0A, (read_rtc_register(0x0A) &0xF0) | 0x03);  // set the clock to 8Khz

					enable_rtc_int(RTC_Vector);
				}

				// get the current timer settings
				oldTimerSettingsA = read_rtc_register(0x0A);
				oldTimerSettingsB = read_rtc_register(0x0B);

				// timer settings less then 3 are not normally used, and aren't faster than setting 3
				if ((oldTimerSettingsA & 0x0F) < 3)
				{
					KdPrint( ("RTInterrupt.sys: It looks like the RTC has been corrupted, old Register A value 0x%02X, setting it to 0x23\n", oldTimerSettingsA) );
					oldTimerSettingsA = 0x23;
				}

				write_rtc_register(0x0A, (oldTimerSettingsA & 0xF0) | 0x03); // set the clock to 8Khz

				timerTicks = 1 << ((oldTimerSettingsA & 0xF) - 3); // how many ticks do we need to wait until we call window's handler

				ticks = timerTicks;

				// if the RTC_Vector is mapped into a space higher than normal and the timer doesn't seem to be set, then reprogram the PIC
				if (useOldInterrupt)
				{
					// so we need to make a new interrupt so that we can still trigger the system interrupt
					BackupInterrupt(OldInterrupt_Vector, &OldInterrupt_Vector_backup);
					CopyInterrupt(RTC_Vector, OldInterrupt_Vector);
				} else {
					OldInterrupt_Vector = 0;
					write_rtc_register(0x0A, (read_rtc_register(0x0A) &0xF0) | 0x03);  // set the clock to 8Khz

					enable_rtc_int(RTC_Vector);
				}

				// make a backup of the current vector, so that we can we can restore it later
				BackupInterrupt(RTC_Vector,&RTC_Vector_backup);

				// get the current code segment, because this is the segment that the interrupt will
				// need to interrupt into, this should always be 0x0008
				HookInterrupt(RTC_Vector,&RTC_Handler, getCS());

				__asm sti; // enable interrupts again
			} else {
				KdPrint( ("RTInterrupt.sys: Not enough input data.\n") );
				ntStatus = STATUS_UNSUCCESSFUL;
				pIrp->IoStatus.Information = 0;
			}
		  break;

      default:
            KdPrint( ("RTInterrupt.sys: Unsupported IOCTL Call\n") );
            ntStatus = STATUS_UNSUCCESSFUL;
            pIrp->IoStatus.Information = 0;
            break;

    }

	pIrp->IoStatus.Status = ntStatus;
    IoCompleteRequest( pIrp, IO_NO_INCREMENT );
    return ntStatus;
}

NTSTATUS DriverEntry(
    IN PDRIVER_OBJECT DriverObject,
    IN PUNICODE_STRING RegistryPath
    )
{
	PDEVICE_OBJECT		DeviceObject;
	NTSTATUS			status;

	UNICODE_STRING		uniNameString, uniDOSString;
	int i;

	KdPrint( ("RTInterrupt.sys: Interrupt.sys Loading\n") );

	RtlInitUnicodeString(&uniNameString, NameBuffer);
	RtlInitUnicodeString(&uniDOSString, DOSNameBuffer);

	status = IoCreateDevice(DriverObject,			// DriverObject
					0,	// DeviceExtensionSize
					&uniNameString,			// DeviceName
					FILE_DEVICE_UNKNOWN,		// DeviceType
					0,					// DeviceCharacteristics
					TRUE,					// Exclusive
					&DeviceObject);			// *DeviceObject

	if(!NT_SUCCESS(status)) return status;

	status = IoCreateSymbolicLink (&uniDOSString, &uniNameString);

	if (!NT_SUCCESS(status)) return status;

	for (i=0;i<NRYIELDAPS;i++) YieldAPCs[i].Thread = NULL;

	DriverObject->MajorFunction[IRP_MJ_CREATE] = InterruptCreateDispatch;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = InterruptDeviceControl;
  	DriverObject->DriverUnload = InterruptUnload;

	return STATUS_SUCCESS;
}
