/*
 * PROJECT:     ReactOS i8042 (ps/2 keyboard-mouse controller) driver
 * LICENSE:     GPL - See COPYING in the top level directory
 * FILE:        drivers/input/i8042prt/keyboard.c
 * PURPOSE:     Keyboard specific functions
 * PROGRAMMERS: Copyright Victor Kirhenshtein (sauros@iname.com)
                Copyright Jason Filby (jasonfilby@yahoo.com)
                Copyright Martijn Vernooij (o112w8r02@sneakemail.com)
                Copyright 2006-2007 Hervé Poussineau (hpoussin@reactos.org)
 */

/* INCLUDES ****************************************************************/

#include "i8042prt.h"

/* GLOBALS *******************************************************************/

static IO_WORKITEM_ROUTINE i8042DebugWorkItem;
static IO_WORKITEM_ROUTINE i8042PowerWorkItem;

/* This structure starts with the same layout as KEYBOARD_INDICATOR_TRANSLATION */
typedef struct _LOCAL_KEYBOARD_INDICATOR_TRANSLATION {
	USHORT NumberOfIndicatorKeys;
	INDICATOR_LIST IndicatorList[3];
} LOCAL_KEYBOARD_INDICATOR_TRANSLATION, *PLOCAL_KEYBOARD_INDICATOR_TRANSLATION;

static LOCAL_KEYBOARD_INDICATOR_TRANSLATION IndicatorTranslation = { 3, {
	{0x3A, KEYBOARD_CAPS_LOCK_ON},
	{0x45, KEYBOARD_NUM_LOCK_ON},
	{0x46, KEYBOARD_SCROLL_LOCK_ON}}};

/* FUNCTIONS *****************************************************************/

/* Debug stuff */
#define TAG(A, B, C, D) (ULONG)(((A)<<0) + ((B)<<8) + ((C)<<16) + ((D)<<24))

static VOID NTAPI
i8042DebugWorkItem(
	IN PDEVICE_OBJECT DeviceObject,
	IN PVOID Key)
{
	DPRINT("Debug key: 0x%02x\n", Key);

	if (!Key)
		return;

	/* We hope kernel would understand this. If
	 * that's not the case, nothing would happen.
	 */
	KdSystemDebugControl(TAG('R', 'o', 's', ' '), Key, 0, NULL, 0, NULL, KernelMode);
}

/*
 * These functions are callbacks for filter driver custom interrupt
 * service routines.
 */
/*static VOID NTAPI
i8042KbdIsrWritePort(
	IN PVOID Context,
	IN UCHAR Value)
{
	PI8042_KEYBOARD_EXTENSION DeviceExtension;

	DeviceExtension = (PI8042_KEYBOARD_EXTENSION)Context;

	if (DeviceExtension->KeyboardHook.IsrWritePort)
	{
		DeviceExtension->KeyboardHook.IsrWritePort(
			DeviceExtension->KeyboardHook.CallContext,
			Value);
	}
	else
		i8042IsrWritePort(Context, Value, 0);
}*/

static VOID NTAPI
i8042KbdQueuePacket(
	IN PVOID Context)
{
	PI8042_KEYBOARD_EXTENSION DeviceExtension;

	DeviceExtension = (PI8042_KEYBOARD_EXTENSION)Context;

	DeviceExtension->KeyComplete = TRUE;
	DeviceExtension->KeysInBuffer++;
	if (DeviceExtension->KeysInBuffer > DeviceExtension->Common.PortDeviceExtension->Settings.KeyboardDataQueueSize)
	{
		DPRINT1("Keyboard buffer overflow\n");
		DeviceExtension->KeysInBuffer--;
	}

	DPRINT("Irq completes key\n");
	KeInsertQueueDpc(&DeviceExtension->DpcKeyboard, NULL, NULL);
}

/*
 * These functions are callbacks for filter driver custom
 * initialization routines.
 */
NTSTATUS DDKAPI
i8042SynchWritePortKbd(
	IN PVOID Context,
	IN UCHAR Value,
	IN BOOLEAN WaitForAck)
{
	return i8042SynchWritePort(
		(PPORT_DEVICE_EXTENSION)Context,
		0,
		Value,
		WaitForAck);
}

/*
 * Process the keyboard internal device requests
 */
VOID NTAPI
i8042KbdStartIo(
	IN PDEVICE_OBJECT DeviceObject,
	IN PIRP Irp)
{
	PIO_STACK_LOCATION Stack;
	PI8042_KEYBOARD_EXTENSION DeviceExtension;
	PPORT_DEVICE_EXTENSION PortDeviceExtension;

	Stack = IoGetCurrentIrpStackLocation(Irp);
	DeviceExtension = (PI8042_KEYBOARD_EXTENSION)DeviceObject->DeviceExtension;
	PortDeviceExtension = DeviceExtension->Common.PortDeviceExtension;

	switch (Stack->Parameters.DeviceIoControl.IoControlCode)
	{
		case IOCTL_KEYBOARD_SET_INDICATORS:
		{
			DPRINT("IOCTL_KEYBOARD_SET_INDICATORS\n");

			PortDeviceExtension->PacketBuffer[0] = KBD_CMD_SET_LEDS;
			PortDeviceExtension->PacketBuffer[1] = 0;
			if (DeviceExtension->KeyboardIndicators.LedFlags & KEYBOARD_CAPS_LOCK_ON)
				PortDeviceExtension->PacketBuffer[1] |= KBD_LED_CAPS;

			if (DeviceExtension->KeyboardIndicators.LedFlags & KEYBOARD_NUM_LOCK_ON)
				PortDeviceExtension->PacketBuffer[1] |= KBD_LED_NUM;

			if (DeviceExtension->KeyboardIndicators.LedFlags & KEYBOARD_SCROLL_LOCK_ON)
				PortDeviceExtension->PacketBuffer[1] |= KBD_LED_SCROLL;

			i8042StartPacket(
				PortDeviceExtension,
				&DeviceExtension->Common,
				PortDeviceExtension->PacketBuffer,
				2,
				Irp);
			break;
		}
		default:
		{
			DPRINT("Unknown ioctl code 0x%lx\n",
				Stack->Parameters.DeviceIoControl.IoControlCode);
			ASSERT(FALSE);
		}
	}
}

static VOID
i8042PacketDpc(
	IN PPORT_DEVICE_EXTENSION DeviceExtension)
{
	BOOLEAN FinishIrp = FALSE;
	KIRQL Irql;
	NTSTATUS Result = STATUS_INTERNAL_ERROR; /* Shouldn't happen */

	/* If the interrupt happens before this is setup, the key
	 * was already in the buffer. Too bad! */
	if (!DeviceExtension->HighestDIRQLInterrupt)
		return;

	Irql = KeAcquireInterruptSpinLock(DeviceExtension->HighestDIRQLInterrupt);

	if (DeviceExtension->Packet.State == Idle
	 && DeviceExtension->PacketComplete)
	{
		FinishIrp = TRUE;
		Result = DeviceExtension->PacketResult;
		DeviceExtension->PacketComplete = FALSE;
	}

	KeReleaseInterruptSpinLock(DeviceExtension->HighestDIRQLInterrupt, Irql);

	if (!FinishIrp)
		return;

	if (DeviceExtension->CurrentIrp)
	{
		DeviceExtension->CurrentIrp->IoStatus.Status = Result;
		IoCompleteRequest(DeviceExtension->CurrentIrp, IO_NO_INCREMENT);
		IoStartNextPacket(DeviceExtension->CurrentIrpDevice, FALSE);
		DeviceExtension->CurrentIrp = NULL;
		DeviceExtension->CurrentIrpDevice = NULL;
	}
}

static VOID NTAPI
i8042PowerWorkItem(
	IN PDEVICE_OBJECT DeviceObject,
	IN PVOID Context)
{
	PI8042_KEYBOARD_EXTENSION DeviceExtension;
	PIRP WaitingIrp;
	NTSTATUS Status;

	DeviceExtension = (PI8042_KEYBOARD_EXTENSION)Context;

	/* See http://blogs.msdn.com/doronh/archive/2006/09/08/746961.aspx */

	/* Register GUID_DEVICE_SYS_BUTTON interface and report capability */
	if (DeviceExtension->NewCaps != DeviceExtension->ReportedCaps)
	{
		WaitingIrp = InterlockedExchangePointer(&DeviceExtension->PowerIrp, NULL);
		if (WaitingIrp)
		{
			/* Cancel the current power irp, as capability changed */
			WaitingIrp->IoStatus.Status = STATUS_UNSUCCESSFUL;
			WaitingIrp->IoStatus.Information = sizeof(ULONG);
			IoCompleteRequest(WaitingIrp, IO_NO_INCREMENT);
		}

		if (DeviceExtension->PowerInterfaceName.MaximumLength == 0)
		{
			/* We have never registred this interface ; do it */
			Status = IoRegisterDeviceInterface(
				DeviceExtension->Common.Pdo,
				&GUID_DEVICE_SYS_BUTTON,
				NULL,
				&DeviceExtension->PowerInterfaceName);
			if (!NT_SUCCESS(Status))
			{
				/* We can't do more yet, ignore the keypress... */
				DPRINT("IoRegisterDeviceInterface(GUID_DEVICE_SYS_BUTTON) failed with status 0x%08lx\n",
					Status);
				DeviceExtension->PowerInterfaceName.MaximumLength = 0;
				return;
			}
		}
		else
		{
			/* Disable the interface. Once activated again, capabilities would be asked again */
			Status = IoSetDeviceInterfaceState(
				&DeviceExtension->PowerInterfaceName,
				FALSE);
			if (!NT_SUCCESS(Status))
			{
				/* Ignore the key press... */
				DPRINT("Disabling interface %wZ failed with status 0x%08lx\n",
					&DeviceExtension->PowerInterfaceName, Status);
				return;
			}
		}
		/* Enable the interface. This leads to receving a IOCTL_GET_SYS_BUTTON_CAPS,
		 * so we can report new capability */
		Status = IoSetDeviceInterfaceState(
				&DeviceExtension->PowerInterfaceName,
				TRUE);
		if (!NT_SUCCESS(Status))
		{
			/* Ignore the key press... */
			DPRINT("Enabling interface %wZ failed with status 0x%08lx\n",
					&DeviceExtension->PowerInterfaceName, Status);
			return;
		}
	}

	/* Directly complete the IOCTL_GET_SYS_BUTTON_EVENT Irp (if any) */
	WaitingIrp = InterlockedExchangePointer(&DeviceExtension->PowerIrp, NULL);
	if (WaitingIrp)
	{
		PULONG pEvent = (PULONG)WaitingIrp->AssociatedIrp.SystemBuffer;

		WaitingIrp->IoStatus.Status = STATUS_SUCCESS;
		WaitingIrp->IoStatus.Information = sizeof(ULONG);
		*pEvent = InterlockedExchange((PLONG)&DeviceExtension->LastPowerKey, 0);
		IoCompleteRequest(WaitingIrp, IO_NO_INCREMENT);
	}
}

/* Return TRUE if it was a power key */
static BOOLEAN
HandlePowerKeys(
	IN PI8042_KEYBOARD_EXTENSION DeviceExtension)
{
	PKEYBOARD_INPUT_DATA InputData;
	ULONG KeyPress;

	InputData = DeviceExtension->KeyboardBuffer + DeviceExtension->KeysInBuffer - 1;
	if (!(InputData->Flags & KEY_E0))
		return FALSE;

	if (InputData->Flags & KEY_BREAK)
		/* We already took care of the key press */
		return TRUE;

	switch (InputData->MakeCode)
	{
		case KEYBOARD_POWER_CODE:
			KeyPress = SYS_BUTTON_POWER;
			break;
		case KEYBOARD_SLEEP_CODE:
			KeyPress = SYS_BUTTON_SLEEP;
			break;
		case KEYBOARD_WAKE_CODE:
			KeyPress = SYS_BUTTON_WAKE;
			break;
		default:
			return FALSE;
	}

	/* Our work can only be done at passive level, so use a workitem */
	DeviceExtension->NewCaps |= KeyPress;
	InterlockedExchange((PLONG)&DeviceExtension->LastPowerKey, KeyPress);
	IoQueueWorkItem(
		DeviceExtension->PowerWorkItem,
		&i8042PowerWorkItem,
		DelayedWorkQueue,
		DeviceExtension);
	return TRUE;
}

static VOID NTAPI
i8042KbdDpcRoutine(
	IN PKDPC Dpc,
	IN PVOID DeferredContext,
	IN PVOID SystemArgument1,
	IN PVOID SystemArgument2)
{
	PI8042_KEYBOARD_EXTENSION DeviceExtension;
	PPORT_DEVICE_EXTENSION PortDeviceExtension;
	ULONG KeysTransferred = 0;
	ULONG KeysInBufferCopy;
	KIRQL Irql;

	DeviceExtension = (PI8042_KEYBOARD_EXTENSION)DeferredContext;
	PortDeviceExtension = DeviceExtension->Common.PortDeviceExtension;

	if (HandlePowerKeys(DeviceExtension))
	{
		DeviceExtension->KeyComplete = FALSE;
		return;
	}

	i8042PacketDpc(PortDeviceExtension);
	if (!DeviceExtension->KeyComplete)
		return;
	/* We got the interrupt as it was being enabled, too bad */
	if (!PortDeviceExtension->HighestDIRQLInterrupt)
		return;

	Irql = KeAcquireInterruptSpinLock(PortDeviceExtension->HighestDIRQLInterrupt);

	DeviceExtension->KeyComplete = FALSE;
	KeysInBufferCopy = DeviceExtension->KeysInBuffer;

	KeReleaseInterruptSpinLock(PortDeviceExtension->HighestDIRQLInterrupt, Irql);

	if (PortDeviceExtension->Settings.CrashOnCtrlScroll)
	{
		PKEYBOARD_INPUT_DATA InputData;
		InputData = DeviceExtension->KeyboardBuffer + KeysInBufferCopy - 1;

		/* Test for TAB + key combination */
		if (InputData->MakeCode == 0x0F)
			DeviceExtension->TabPressed = !(InputData->Flags & KEY_BREAK);
		else if (DeviceExtension->TabPressed)
		{
			DeviceExtension->TabPressed = FALSE;

			IoQueueWorkItem(
				DeviceExtension->DebugWorkItem,
				&i8042DebugWorkItem,
				DelayedWorkQueue,
				(PVOID)(ULONG_PTR)InputData->MakeCode);
		}
	}

	DPRINT("Send a key\n");

	if (!DeviceExtension->KeyboardData.ClassService)
		return;

	DPRINT("Sending %lu key(s)\n", KeysInBufferCopy);
	(*(PSERVICE_CALLBACK_ROUTINE)DeviceExtension->KeyboardData.ClassService)(
		DeviceExtension->KeyboardData.ClassDeviceObject,
		DeviceExtension->KeyboardBuffer,
		DeviceExtension->KeyboardBuffer + KeysInBufferCopy,
		&KeysTransferred);

	KeAcquireInterruptSpinLock(PortDeviceExtension->HighestDIRQLInterrupt);
	DeviceExtension->KeysInBuffer -= KeysTransferred;
	KeReleaseInterruptSpinLock(PortDeviceExtension->HighestDIRQLInterrupt, Irql);
}

/*
 * Runs the keyboard IOCTL dispatch.
 */
NTSTATUS NTAPI
i8042KbdDeviceControl(
	IN PDEVICE_OBJECT DeviceObject,
	IN PIRP Irp)
{
	PIO_STACK_LOCATION Stack;
	PI8042_KEYBOARD_EXTENSION DeviceExtension;
	NTSTATUS Status;

	Stack = IoGetCurrentIrpStackLocation(Irp);
	Irp->IoStatus.Information = 0;
	DeviceExtension = (PI8042_KEYBOARD_EXTENSION)DeviceObject->DeviceExtension;

	switch (Stack->Parameters.DeviceIoControl.IoControlCode)
	{
		case IOCTL_GET_SYS_BUTTON_CAPS:
		{
			/* Part of GUID_DEVICE_SYS_BUTTON interface */
			PULONG pCaps;
			DPRINT("IOCTL_GET_SYS_BUTTON_CAPS\n");

			if (Stack->Parameters.DeviceIoControl.OutputBufferLength != sizeof(ULONG))
				Status = STATUS_INVALID_PARAMETER;
			else
			{
				pCaps = (PULONG)Irp->AssociatedIrp.SystemBuffer;
				*pCaps = DeviceExtension->NewCaps;
				DeviceExtension->ReportedCaps = DeviceExtension->NewCaps;
				Irp->IoStatus.Information = sizeof(ULONG);
				Status = STATUS_SUCCESS;
			}
			break;
		}
		case IOCTL_GET_SYS_BUTTON_EVENT:
		{
			/* Part of GUID_DEVICE_SYS_BUTTON interface */
			PIRP WaitingIrp;
			DPRINT("IOCTL_GET_SYS_BUTTON_EVENT\n");

			if (Stack->Parameters.DeviceIoControl.OutputBufferLength != sizeof(ULONG))
				Status = STATUS_INVALID_PARAMETER;
			else
			{
				WaitingIrp = InterlockedCompareExchangePointer(
					&DeviceExtension->PowerIrp,
					Irp,
					NULL);
				/* Check if an Irp is already pending */
				if (WaitingIrp)
				{
					/* Unable to have a 2nd pending IRP for this IOCTL */
					DPRINT1("Unable to pend a second IRP for IOCTL_GET_SYS_BUTTON_EVENT\n");
					Status = STATUS_INVALID_PARAMETER;
					Irp->IoStatus.Status = Status;
					IoCompleteRequest(Irp, IO_NO_INCREMENT);
				}
				else
				{
					ULONG PowerKey;
					PowerKey = InterlockedExchange((PLONG)&DeviceExtension->LastPowerKey, 0);
					if (PowerKey != 0)
					{
						(VOID)InterlockedCompareExchangePointer(&DeviceExtension->PowerIrp, NULL, Irp);
						*(PULONG)Irp->AssociatedIrp.SystemBuffer = PowerKey;
						Status = STATUS_SUCCESS;
						Irp->IoStatus.Status = Status;
						Irp->IoStatus.Information = sizeof(ULONG);
						IoCompleteRequest(Irp, IO_NO_INCREMENT);
					}
					else
					{
						DPRINT("Pending IOCTL_GET_SYS_BUTTON_EVENT\n");
						Status = STATUS_PENDING;
						Irp->IoStatus.Status = Status;
						IoMarkIrpPending(Irp);
					}
				}
				return Status;
			}
			break;
		}
		default:
		{
			DPRINT("IRP_MJ_DEVICE_CONTROL / unknown ioctl code 0x%lx\n",
				Stack->Parameters.DeviceIoControl.IoControlCode);
			ASSERT(FALSE);
			return ForwardIrpAndForget(DeviceObject, Irp);
		}
	}

	Irp->IoStatus.Status = Status;
	if (Status == STATUS_PENDING)
		IoMarkIrpPending(Irp);
	else
		IoCompleteRequest(Irp, IO_NO_INCREMENT);

	return Status;
}

/*
 * Runs the keyboard IOCTL_INTERNAL dispatch.
 */
NTSTATUS NTAPI
i8042KbdInternalDeviceControl(
	IN PDEVICE_OBJECT DeviceObject,
	IN PIRP Irp)
{
	PIO_STACK_LOCATION Stack;
	PI8042_KEYBOARD_EXTENSION DeviceExtension;
	NTSTATUS Status;

	Stack = IoGetCurrentIrpStackLocation(Irp);
	Irp->IoStatus.Information = 0;
	DeviceExtension = (PI8042_KEYBOARD_EXTENSION)DeviceObject->DeviceExtension;

	switch (Stack->Parameters.DeviceIoControl.IoControlCode)
	{
		case IOCTL_INTERNAL_KEYBOARD_CONNECT:
		{
			SIZE_T Size;
			PIO_WORKITEM WorkItem = NULL;
			PI8042_HOOK_WORKITEM WorkItemData = NULL;

			DPRINT("IRP_MJ_INTERNAL_DEVICE_CONTROL / IOCTL_INTERNAL_KEYBOARD_CONNECT\n");
			if (Stack->Parameters.DeviceIoControl.InputBufferLength != sizeof(CONNECT_DATA))
			{
				Status = STATUS_INVALID_PARAMETER;
				goto cleanup;
			}

			DeviceExtension->KeyboardData =
				*((PCONNECT_DATA)Stack->Parameters.DeviceIoControl.Type3InputBuffer);

			/* Send IOCTL_INTERNAL_I8042_HOOK_KEYBOARD to device stack */
			WorkItem = IoAllocateWorkItem(DeviceObject);
			if (!WorkItem)
			{
				DPRINT("IoAllocateWorkItem() failed\n");
				Status = STATUS_INSUFFICIENT_RESOURCES;
				goto cleanup;
			}
			WorkItemData = ExAllocatePoolWithTag(
				NonPagedPool,
				sizeof(I8042_HOOK_WORKITEM),
				I8042PRT_TAG);
			if (!WorkItemData)
			{
				DPRINT("ExAllocatePoolWithTag() failed\n");
				Status = STATUS_NO_MEMORY;
				goto cleanup;
			}
			WorkItemData->WorkItem = WorkItem;
			WorkItemData->Irp = Irp;

			/* Initialize extension */
			DeviceExtension->Common.Type = Keyboard;
			Size = DeviceExtension->Common.PortDeviceExtension->Settings.KeyboardDataQueueSize * sizeof(KEYBOARD_INPUT_DATA);
			DeviceExtension->KeyboardBuffer = ExAllocatePoolWithTag(
				NonPagedPool,
				Size,
				I8042PRT_TAG);
			if (!DeviceExtension->KeyboardBuffer)
			{
				DPRINT("ExAllocatePoolWithTag() failed\n");
				Status = STATUS_NO_MEMORY;
				goto cleanup;
			}
			RtlZeroMemory(DeviceExtension->KeyboardBuffer, Size);
			KeInitializeDpc(
				&DeviceExtension->DpcKeyboard,
				i8042KbdDpcRoutine,
				DeviceExtension);
			DeviceExtension->PowerWorkItem = IoAllocateWorkItem(DeviceObject);
			if (!DeviceExtension->PowerWorkItem)
			{
				DPRINT("IoAllocateWorkItem() failed\n");
				Status = STATUS_INSUFFICIENT_RESOURCES;
				goto cleanup;
			}
			DeviceExtension->DebugWorkItem = IoAllocateWorkItem(DeviceObject);
			if (!DeviceExtension->DebugWorkItem)
			{
				DPRINT("IoAllocateWorkItem() failed\n");
				Status = STATUS_INSUFFICIENT_RESOURCES;
				goto cleanup;
			}
			DeviceExtension->Common.PortDeviceExtension->KeyboardExtension = DeviceExtension;
			DeviceExtension->Common.PortDeviceExtension->Flags |= KEYBOARD_CONNECTED;

			IoMarkIrpPending(Irp);
			/* FIXME: DeviceExtension->KeyboardHook.IsrWritePort = ; */
			DeviceExtension->KeyboardHook.QueueKeyboardPacket = i8042KbdQueuePacket;
			DeviceExtension->KeyboardHook.CallContext = DeviceExtension;
			IoQueueWorkItem(WorkItem,
				i8042SendHookWorkItem,
				DelayedWorkQueue,
				WorkItemData);
			Status = STATUS_PENDING;
			break;

cleanup:
			if (DeviceExtension->KeyboardBuffer)
				ExFreePoolWithTag(DeviceExtension->KeyboardBuffer, I8042PRT_TAG);
			if (DeviceExtension->PowerWorkItem)
				IoFreeWorkItem(DeviceExtension->PowerWorkItem);
			if (DeviceExtension->DebugWorkItem)
				IoFreeWorkItem(DeviceExtension->DebugWorkItem);
			if (WorkItem)
				IoFreeWorkItem(WorkItem);
			if (WorkItemData)
				ExFreePoolWithTag(WorkItemData, I8042PRT_TAG);
			break;
		}
		case IOCTL_INTERNAL_KEYBOARD_DISCONNECT:
		{
			DPRINT("IRP_MJ_INTERNAL_DEVICE_CONTROL / IOCTL_INTERNAL_KEYBOARD_DISCONNECT\n");
			/* MSDN says that operation is to implemented.
			 * To implement it, we just have to do:
			 * DeviceExtension->KeyboardData.ClassService = NULL;
			 */
			Status = STATUS_NOT_IMPLEMENTED;
			break;
		}
		case IOCTL_INTERNAL_I8042_HOOK_KEYBOARD:
		{
			DPRINT("IRP_MJ_INTERNAL_DEVICE_CONTROL / IOCTL_INTERNAL_I8042_HOOK_KEYBOARD\n");
			/* Nothing to do here */
			Status = STATUS_SUCCESS;
			break;
		}
		case IOCTL_KEYBOARD_QUERY_INDICATOR_TRANSLATION:
		{
			DPRINT("IRP_MJ_INTERNAL_DEVICE_CONTROL / IOCTL_KEYBOARD_QUERY_INDICATOR_TRANSLATION\n");

			/* We should check the UnitID, but it's kind of pointless as
			 * all keyboards are supposed to have the same one
			 */
			if (Stack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(LOCAL_KEYBOARD_INDICATOR_TRANSLATION))
			{
				Status = STATUS_BUFFER_TOO_SMALL;
			}
			else
			{
				RtlCopyMemory(
					Irp->AssociatedIrp.SystemBuffer,
					&IndicatorTranslation,
					sizeof(LOCAL_KEYBOARD_INDICATOR_TRANSLATION));
				Irp->IoStatus.Information = sizeof(LOCAL_KEYBOARD_INDICATOR_TRANSLATION);
				Status = STATUS_SUCCESS;
			}
			break;
		}
		case IOCTL_KEYBOARD_QUERY_INDICATORS:
		{
			DPRINT("IRP_MJ_INTERNAL_DEVICE_CONTROL / IOCTL_KEYBOARD_QUERY_INDICATORS\n");

			if (Stack->Parameters.DeviceIoControl.InputBufferLength < sizeof(KEYBOARD_INDICATOR_PARAMETERS))
			{
				Status = STATUS_BUFFER_TOO_SMALL;
			}
			else
			{
				RtlCopyMemory(
					Irp->AssociatedIrp.SystemBuffer,
					&DeviceExtension->KeyboardIndicators,
					sizeof(KEYBOARD_INDICATOR_PARAMETERS));
				Irp->IoStatus.Information = sizeof(LOCAL_KEYBOARD_INDICATOR_TRANSLATION);
				Status = STATUS_SUCCESS;
			}
			break;
		}
		case IOCTL_KEYBOARD_SET_INDICATORS:
		{
			DPRINT("IRP_MJ_INTERNAL_DEVICE_CONTROL / IOCTL_KEYBOARD_SET_INDICATORS\n");

			if (Stack->Parameters.DeviceIoControl.InputBufferLength < sizeof(KEYBOARD_INDICATOR_PARAMETERS))
			{
				Status = STATUS_BUFFER_TOO_SMALL;
			}
			else
			{
				RtlCopyMemory(
					&DeviceExtension->KeyboardIndicators,
					Irp->AssociatedIrp.SystemBuffer,
					sizeof(KEYBOARD_INDICATOR_PARAMETERS));
				Status = STATUS_PENDING;
				IoMarkIrpPending(Irp);
				IoStartPacket(DeviceObject, Irp, NULL, NULL);
			}
			break;
		}
		default:
		{
			DPRINT("IRP_MJ_INTERNAL_DEVICE_CONTROL / unknown ioctl code 0x%lx\n",
				Stack->Parameters.DeviceIoControl.IoControlCode);
			ASSERT(FALSE);
			return ForwardIrpAndForget(DeviceObject, Irp);
		}
	}

	Irp->IoStatus.Status = Status;
	if (Status != STATUS_PENDING)
		IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return Status;
}

/*
 * Call the customization hook. The ToReturn parameter is about wether
 * we should go on with the interrupt. The return value is what
 * we should return (indicating to the system wether someone else
 * should try to handle the interrupt)
 */
static BOOLEAN
i8042KbdCallIsrHook(
	IN PI8042_KEYBOARD_EXTENSION DeviceExtension,
	IN UCHAR Status,
	IN UCHAR Input,
	OUT PBOOLEAN ToReturn)
{
	BOOLEAN HookReturn, HookContinue;

	HookContinue = FALSE;

	if (DeviceExtension->KeyboardHook.IsrRoutine)
	{
		HookReturn = DeviceExtension->KeyboardHook.IsrRoutine(
			DeviceExtension->KeyboardHook.Context,
			DeviceExtension->KeyboardBuffer + DeviceExtension->KeysInBuffer,
			&DeviceExtension->Common.PortDeviceExtension->Packet,
			Status,
			&Input,
			&HookContinue,
			&DeviceExtension->KeyboardScanState);

		if (!HookContinue)
		{
			*ToReturn = HookReturn;
			return TRUE;
		}
	}
	return FALSE;
}

BOOLEAN NTAPI
i8042KbdInterruptService(
	IN PKINTERRUPT Interrupt,
	PVOID Context)
{
	PI8042_KEYBOARD_EXTENSION DeviceExtension;
	PPORT_DEVICE_EXTENSION PortDeviceExtension;
	PKEYBOARD_INPUT_DATA InputData;
	ULONG Counter;
	UCHAR PortStatus, Output;
	BOOLEAN ToReturn = FALSE;
	NTSTATUS Status;

	DeviceExtension = (PI8042_KEYBOARD_EXTENSION)Context;
	PortDeviceExtension = DeviceExtension->Common.PortDeviceExtension;
	InputData = DeviceExtension->KeyboardBuffer + DeviceExtension->KeysInBuffer;
	Counter = PortDeviceExtension->Settings.PollStatusIterations;

	while (Counter)
	{
		Status = i8042ReadStatus(PortDeviceExtension, &PortStatus);
		if (!NT_SUCCESS(Status))
		{
			DPRINT("i8042ReadStatus() failed with status 0x%08lx\n", Status);
			return FALSE;
		}
		Status = i8042ReadKeyboardData(PortDeviceExtension, &Output);
		if (NT_SUCCESS(Status))
			break;
		KeStallExecutionProcessor(1);
		Counter--;
	}
	if (Counter == 0)
	{
		DPRINT("Spurious i8042 keyboard interrupt\n");
		return FALSE;
	}

	DPRINT("Got: 0x%02x\n", Output);

	if (PortDeviceExtension->Settings.CrashOnCtrlScroll)
	{
		/* Test for CTRL + SCROLL LOCK twice */
		static const UCHAR ScanCodes[] = { 0x1d, 0x46, 0x46, 0 };

		if (!(Output & 0x80))
		{
			if ((Output & 0x7f) == ScanCodes[DeviceExtension->ComboPosition])
			{
				DeviceExtension->ComboPosition++;
				if (ScanCodes[DeviceExtension->ComboPosition] == 0)
					KeBugCheck(MANUALLY_INITIATED_CRASH);
			}
			else if ((Output & 0x7f) == ScanCodes[0])
				DeviceExtension->ComboPosition = 1;
			else
				DeviceExtension->ComboPosition = 0;
		}
	}

	if (i8042KbdCallIsrHook(DeviceExtension, PortStatus, Output, &ToReturn))
		return ToReturn;

	if (i8042PacketIsr(PortDeviceExtension, Output))
	{
		if (PortDeviceExtension->PacketComplete)
		{
			DPRINT("Packet complete\n");
			KeInsertQueueDpc(&DeviceExtension->DpcKeyboard, NULL, NULL);
		}
		DPRINT("Irq eaten by packet\n");
		return TRUE;
	}

	DPRINT("Irq is keyboard input\n");

	if (DeviceExtension->KeyboardScanState == Normal)
	{
		switch (Output)
		{
			case 0xe0:
				DeviceExtension->KeyboardScanState = GotE0;
				return TRUE;
			case 0xe1:
				DeviceExtension->KeyboardScanState = GotE1;
				return TRUE;
			default:
				break;
		}
	}

	/* Update InputData */
	InputData->Flags = 0;
	switch (DeviceExtension->KeyboardScanState)
	{
		case GotE0:
			InputData->Flags |= KEY_E0;
			break;
		case GotE1:
			InputData->Flags |= KEY_E1;
			break;
		default:
			break;
	}
	DeviceExtension->KeyboardScanState = Normal;
	if (Output & 0x80)
		InputData->Flags |= KEY_BREAK;
	else
		InputData->Flags |= KEY_MAKE;
	InputData->MakeCode = Output & 0x7f;
	InputData->Reserved = 0;

	DeviceExtension->KeyboardHook.QueueKeyboardPacket(DeviceExtension->KeyboardHook.CallContext);

	return TRUE;
}
