mirror of
https://github.com/reactos/reactos.git
synced 2024-12-28 01:55:19 +00:00
973 lines
29 KiB
C
973 lines
29 KiB
C
/*
|
|
* PROJECT: ReactOS i8042 (ps/2 keyboard-mouse controller) driver
|
|
* LICENSE: GPL - See COPYING in the top level directory
|
|
* FILE: drivers/input/i8042prt/mouse.c
|
|
* PURPOSE: Mouse 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)
|
|
Copyright 2008 Colin Finck (mail@colinfinck.de)
|
|
*/
|
|
|
|
/* INCLUDES ****************************************************************/
|
|
|
|
#include "i8042prt.h"
|
|
|
|
#include <debug.h>
|
|
|
|
/* FUNCTIONS *****************************************************************/
|
|
|
|
static KDEFERRED_ROUTINE i8042MouDpcRoutine;
|
|
static KDEFERRED_ROUTINE i8042DpcRoutineMouseTimeout;
|
|
|
|
/*
|
|
* These functions are callbacks for filter driver custom interrupt
|
|
* service routines.
|
|
*/
|
|
static VOID NTAPI
|
|
i8042MouIsrWritePort(
|
|
IN PVOID Context,
|
|
IN UCHAR Value)
|
|
{
|
|
PI8042_MOUSE_EXTENSION DeviceExtension;
|
|
|
|
DeviceExtension = (PI8042_MOUSE_EXTENSION)Context;
|
|
|
|
if (DeviceExtension->MouseHook.IsrWritePort != i8042MouIsrWritePort)
|
|
{
|
|
DeviceExtension->MouseHook.IsrWritePort(
|
|
DeviceExtension->MouseHook.CallContext,
|
|
Value);
|
|
}
|
|
else
|
|
i8042IsrWritePort(DeviceExtension->Common.PortDeviceExtension, Value, CTRL_WRITE_MOUSE);
|
|
}
|
|
|
|
static VOID NTAPI
|
|
i8042MouQueuePacket(
|
|
IN PVOID Context)
|
|
{
|
|
PI8042_MOUSE_EXTENSION DeviceExtension;
|
|
|
|
DeviceExtension = (PI8042_MOUSE_EXTENSION)Context;
|
|
|
|
DeviceExtension->MouseComplete = TRUE;
|
|
DeviceExtension->MouseInBuffer++;
|
|
if (DeviceExtension->MouseInBuffer >= DeviceExtension->Common.PortDeviceExtension->Settings.MouseDataQueueSize)
|
|
{
|
|
WARN_(I8042PRT, "Mouse buffer overflow\n");
|
|
DeviceExtension->MouseInBuffer--;
|
|
}
|
|
|
|
TRACE_(I8042PRT, "Irq completes mouse packet\n");
|
|
KeInsertQueueDpc(&DeviceExtension->DpcMouse, NULL, NULL);
|
|
}
|
|
|
|
VOID
|
|
i8042MouHandle(
|
|
IN PI8042_MOUSE_EXTENSION DeviceExtension,
|
|
IN UCHAR Output)
|
|
{
|
|
PMOUSE_INPUT_DATA MouseInput;
|
|
CHAR Scroll;
|
|
|
|
MouseInput = DeviceExtension->MouseBuffer + DeviceExtension->MouseInBuffer;
|
|
|
|
switch (DeviceExtension->MouseState)
|
|
{
|
|
case MouseIdle:
|
|
/* This bit should be 1, if not drop the packet, we
|
|
* might be lucky and get in sync again
|
|
*/
|
|
if (!(Output & 8)) {
|
|
WARN_(I8042PRT, "Bad input, dropping..\n");
|
|
return;
|
|
}
|
|
|
|
MouseInput->Buttons = 0;
|
|
MouseInput->RawButtons = 0;
|
|
MouseInput->Flags = MOUSE_MOVE_RELATIVE;
|
|
|
|
/* Note how we ignore the overflow bits, like Windows
|
|
* is said to do. There's no reasonable thing to do
|
|
* anyway.
|
|
*/
|
|
|
|
if (Output & 16)
|
|
MouseInput->LastX = 1;
|
|
else
|
|
MouseInput->LastX = 0;
|
|
if (Output & 32)
|
|
MouseInput->LastY = 1;
|
|
else
|
|
MouseInput->LastY = 0;
|
|
|
|
if (Output & 1)
|
|
MouseInput->RawButtons |= MOUSE_LEFT_BUTTON_DOWN;
|
|
if (Output & 2)
|
|
MouseInput->RawButtons |= MOUSE_RIGHT_BUTTON_DOWN;
|
|
if (Output & 4)
|
|
MouseInput->RawButtons |= MOUSE_MIDDLE_BUTTON_DOWN;
|
|
|
|
DeviceExtension->MouseState = XMovement;
|
|
break;
|
|
|
|
case XMovement:
|
|
if (MouseInput->LastX)
|
|
MouseInput->LastX = (LONG) Output - 256;
|
|
else
|
|
MouseInput->LastX = Output;
|
|
|
|
DeviceExtension->MouseState = YMovement;
|
|
break;
|
|
|
|
case YMovement:
|
|
if (MouseInput->LastY)
|
|
MouseInput->LastY = (LONG)Output - 256;
|
|
else
|
|
MouseInput->LastY = (LONG)Output;
|
|
|
|
/* Windows wants it the other way around */
|
|
MouseInput->LastY = -MouseInput->LastY;
|
|
|
|
if (DeviceExtension->MouseType == GenericPS2 ||
|
|
DeviceExtension->MouseType == Ps2pp)
|
|
{
|
|
i8042MouHandleButtons(
|
|
DeviceExtension,
|
|
MOUSE_LEFT_BUTTON_DOWN |
|
|
MOUSE_RIGHT_BUTTON_DOWN |
|
|
MOUSE_MIDDLE_BUTTON_DOWN);
|
|
DeviceExtension->MouseHook.QueueMousePacket(DeviceExtension->MouseHook.CallContext);
|
|
DeviceExtension->MouseState = MouseIdle;
|
|
}
|
|
else
|
|
{
|
|
DeviceExtension->MouseState = ZMovement;
|
|
}
|
|
break;
|
|
|
|
case ZMovement:
|
|
Scroll = Output & 0x0f;
|
|
if (Scroll & 8)
|
|
Scroll |= 0xf0;
|
|
|
|
if (Scroll)
|
|
{
|
|
MouseInput->RawButtons |= MOUSE_WHEEL;
|
|
MouseInput->ButtonData = (USHORT)(Scroll * -WHEEL_DELTA);
|
|
}
|
|
|
|
if (DeviceExtension->MouseType == IntellimouseExplorer)
|
|
{
|
|
if (Output & 16)
|
|
MouseInput->RawButtons |= MOUSE_BUTTON_4_DOWN;
|
|
if (Output & 32)
|
|
MouseInput->RawButtons |= MOUSE_BUTTON_5_DOWN;
|
|
}
|
|
i8042MouHandleButtons(
|
|
DeviceExtension,
|
|
MOUSE_LEFT_BUTTON_DOWN |
|
|
MOUSE_RIGHT_BUTTON_DOWN |
|
|
MOUSE_MIDDLE_BUTTON_DOWN |
|
|
MOUSE_BUTTON_4_DOWN |
|
|
MOUSE_BUTTON_5_DOWN);
|
|
DeviceExtension->MouseHook.QueueMousePacket(DeviceExtension->MouseHook.CallContext);
|
|
DeviceExtension->MouseState = MouseIdle;
|
|
break;
|
|
|
|
default:
|
|
ERR_(I8042PRT, "Unexpected state 0x%lx!\n", DeviceExtension->MouseState);
|
|
ASSERT(FALSE);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Updates ButtonFlags according to RawButtons and a saved state;
|
|
* Only takes in account the bits that are set in Mask
|
|
*/
|
|
VOID
|
|
i8042MouHandleButtons(
|
|
IN PI8042_MOUSE_EXTENSION DeviceExtension,
|
|
IN USHORT Mask)
|
|
{
|
|
PMOUSE_INPUT_DATA MouseInput;
|
|
USHORT NewButtonData;
|
|
USHORT ButtonDiff;
|
|
|
|
MouseInput = DeviceExtension->MouseBuffer + DeviceExtension->MouseInBuffer;
|
|
NewButtonData = (USHORT)(MouseInput->RawButtons & Mask);
|
|
ButtonDiff = (NewButtonData ^ DeviceExtension->MouseButtonState) & Mask;
|
|
|
|
/* Note that the defines are such:
|
|
* MOUSE_LEFT_BUTTON_DOWN 1
|
|
* MOUSE_LEFT_BUTTON_UP 2
|
|
*/
|
|
MouseInput->ButtonFlags |= (NewButtonData & ButtonDiff) |
|
|
(((~(NewButtonData)) << 1) & (ButtonDiff << 1)) |
|
|
(MouseInput->RawButtons & 0xfc00);
|
|
|
|
INFO_(I8042PRT, "Left raw/up/down: %u/%u/%u\n",
|
|
MouseInput->RawButtons & MOUSE_LEFT_BUTTON_DOWN,
|
|
MouseInput->ButtonFlags & MOUSE_LEFT_BUTTON_DOWN,
|
|
MouseInput->ButtonFlags & MOUSE_LEFT_BUTTON_UP);
|
|
|
|
DeviceExtension->MouseButtonState =
|
|
(DeviceExtension->MouseButtonState & ~Mask) | (NewButtonData & Mask);
|
|
}
|
|
|
|
/* Does final initializations for the mouse. This method
|
|
* is called just before connecting the interrupt.
|
|
*/
|
|
NTSTATUS
|
|
i8042MouInitialize(
|
|
IN PI8042_MOUSE_EXTENSION DeviceExtension)
|
|
{
|
|
NTSTATUS Status;
|
|
UCHAR Value;
|
|
|
|
/* Enable the PS/2 mouse port */
|
|
i8042Write(DeviceExtension->Common.PortDeviceExtension, DeviceExtension->Common.PortDeviceExtension->ControlPort, MOUSE_ENAB);
|
|
|
|
/* Enable the mouse */
|
|
if(!i8042IsrWritePort(DeviceExtension->Common.PortDeviceExtension, MOU_ENAB, CTRL_WRITE_MOUSE))
|
|
{
|
|
WARN_(I8042PRT, "Failed to enable mouse!\n");
|
|
return STATUS_IO_DEVICE_ERROR;
|
|
}
|
|
|
|
Status = i8042ReadDataWait(DeviceExtension->Common.PortDeviceExtension, &Value);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
WARN_(I8042PRT, "Failed to read the response of MOU_ENAB, status 0x%08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if(Value == MOUSE_ACK)
|
|
{
|
|
INFO_(I8042PRT, "Mouse was enabled successfully!\n");
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
WARN_(I8042PRT, "Got 0x%02x instead of 0xFA\n", Value);
|
|
return STATUS_IO_DEVICE_ERROR;
|
|
}
|
|
|
|
static VOID NTAPI
|
|
i8042MouDpcRoutine(
|
|
IN PKDPC Dpc,
|
|
IN PVOID DeferredContext,
|
|
IN PVOID SystemArgument1,
|
|
IN PVOID SystemArgument2)
|
|
{
|
|
PI8042_MOUSE_EXTENSION DeviceExtension;
|
|
PPORT_DEVICE_EXTENSION PortDeviceExtension;
|
|
ULONG MouseTransferred = 0;
|
|
ULONG MouseInBufferCopy;
|
|
KIRQL Irql;
|
|
LARGE_INTEGER Timeout;
|
|
|
|
UNREFERENCED_PARAMETER(Dpc);
|
|
UNREFERENCED_PARAMETER(SystemArgument1);
|
|
UNREFERENCED_PARAMETER(SystemArgument2);
|
|
|
|
__analysis_assume(DeferredContext != NULL);
|
|
DeviceExtension = DeferredContext;
|
|
PortDeviceExtension = DeviceExtension->Common.PortDeviceExtension;
|
|
|
|
switch (DeviceExtension->MouseTimeoutState)
|
|
{
|
|
case TimeoutStart:
|
|
{
|
|
DeviceExtension->MouseTimeoutState = NoChange;
|
|
if (DeviceExtension->MouseTimeoutActive &&
|
|
!KeCancelTimer(&DeviceExtension->TimerMouseTimeout))
|
|
{
|
|
/* The timer fired already, give up */
|
|
DeviceExtension->MouseTimeoutActive = FALSE;
|
|
return;
|
|
}
|
|
|
|
Timeout.QuadPart = -15000000; /* 1.5 seconds, should be enough */
|
|
|
|
KeSetTimer(
|
|
&DeviceExtension->TimerMouseTimeout,
|
|
Timeout,
|
|
&DeviceExtension->DpcMouseTimeout);
|
|
DeviceExtension->MouseTimeoutActive = TRUE;
|
|
return;
|
|
}
|
|
|
|
case TimeoutCancel:
|
|
{
|
|
DeviceExtension->MouseTimeoutState = NoChange;
|
|
KeCancelTimer(&DeviceExtension->TimerMouseTimeout);
|
|
DeviceExtension->MouseTimeoutActive = FALSE;
|
|
}
|
|
|
|
default:
|
|
;/* nothing, don't want a warning */
|
|
}
|
|
|
|
/* Should be unlikely */
|
|
if (!DeviceExtension->MouseComplete)
|
|
return;
|
|
|
|
Irql = KeAcquireInterruptSpinLock(PortDeviceExtension->HighestDIRQLInterrupt);
|
|
|
|
DeviceExtension->MouseComplete = FALSE;
|
|
MouseInBufferCopy = DeviceExtension->MouseInBuffer;
|
|
|
|
KeReleaseInterruptSpinLock(PortDeviceExtension->HighestDIRQLInterrupt, Irql);
|
|
|
|
TRACE_(I8042PRT, "Send a mouse packet\n");
|
|
|
|
if (!DeviceExtension->MouseData.ClassService)
|
|
return;
|
|
|
|
INFO_(I8042PRT, "Sending %lu mouse move(s)\n", MouseInBufferCopy);
|
|
(*(PSERVICE_CALLBACK_ROUTINE)DeviceExtension->MouseData.ClassService)(
|
|
DeviceExtension->MouseData.ClassDeviceObject,
|
|
DeviceExtension->MouseBuffer,
|
|
DeviceExtension->MouseBuffer + MouseInBufferCopy,
|
|
&MouseTransferred);
|
|
|
|
Irql = KeAcquireInterruptSpinLock(PortDeviceExtension->HighestDIRQLInterrupt);
|
|
DeviceExtension->MouseInBuffer -= MouseTransferred;
|
|
if (DeviceExtension->MouseInBuffer)
|
|
RtlMoveMemory(
|
|
DeviceExtension->MouseBuffer,
|
|
DeviceExtension->MouseBuffer + MouseTransferred,
|
|
DeviceExtension->MouseInBuffer * sizeof(MOUSE_INPUT_DATA));
|
|
KeReleaseInterruptSpinLock(PortDeviceExtension->HighestDIRQLInterrupt, Irql);
|
|
}
|
|
|
|
/* This timer DPC will be called when the mouse reset times out.
|
|
* I'll just send the 'disable mouse port' command to the controller
|
|
* and say the mouse doesn't exist.
|
|
*/
|
|
static VOID NTAPI
|
|
i8042DpcRoutineMouseTimeout(
|
|
IN PKDPC Dpc,
|
|
IN PVOID DeferredContext,
|
|
IN PVOID SystemArgument1,
|
|
IN PVOID SystemArgument2)
|
|
{
|
|
PI8042_MOUSE_EXTENSION DeviceExtension;
|
|
PPORT_DEVICE_EXTENSION PortDeviceExtension;
|
|
KIRQL Irql;
|
|
|
|
UNREFERENCED_PARAMETER(Dpc);
|
|
UNREFERENCED_PARAMETER(SystemArgument1);
|
|
UNREFERENCED_PARAMETER(SystemArgument2);
|
|
|
|
__analysis_assume(DeferredContext != NULL);
|
|
DeviceExtension = DeferredContext;
|
|
PortDeviceExtension = DeviceExtension->Common.PortDeviceExtension;
|
|
|
|
Irql = KeAcquireInterruptSpinLock(PortDeviceExtension->HighestDIRQLInterrupt);
|
|
|
|
WARN_(I8042PRT, "Mouse initialization timeout! (substate %x)\n",
|
|
DeviceExtension->MouseResetState);
|
|
|
|
PortDeviceExtension->Flags &= ~MOUSE_PRESENT;
|
|
|
|
KeReleaseInterruptSpinLock(PortDeviceExtension->HighestDIRQLInterrupt, Irql);
|
|
}
|
|
|
|
/*
|
|
* Runs the mouse IOCTL_INTERNAL dispatch.
|
|
*/
|
|
NTSTATUS NTAPI
|
|
i8042MouInternalDeviceControl(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp)
|
|
{
|
|
PIO_STACK_LOCATION Stack;
|
|
PI8042_MOUSE_EXTENSION DeviceExtension;
|
|
NTSTATUS Status;
|
|
|
|
Stack = IoGetCurrentIrpStackLocation(Irp);
|
|
Irp->IoStatus.Information = 0;
|
|
DeviceExtension = (PI8042_MOUSE_EXTENSION)DeviceObject->DeviceExtension;
|
|
|
|
switch (Stack->Parameters.DeviceIoControl.IoControlCode)
|
|
{
|
|
case IOCTL_INTERNAL_MOUSE_CONNECT:
|
|
{
|
|
SIZE_T Size;
|
|
PIO_WORKITEM WorkItem = NULL;
|
|
PI8042_HOOK_WORKITEM WorkItemData = NULL;
|
|
|
|
TRACE_(I8042PRT, "IRP_MJ_INTERNAL_DEVICE_CONTROL / IOCTL_INTERNAL_MOUSE_CONNECT\n");
|
|
if (Stack->Parameters.DeviceIoControl.InputBufferLength != sizeof(CONNECT_DATA))
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto cleanup;
|
|
}
|
|
|
|
DeviceExtension->MouseData =
|
|
*((PCONNECT_DATA)Stack->Parameters.DeviceIoControl.Type3InputBuffer);
|
|
|
|
/* Send IOCTL_INTERNAL_I8042_HOOK_MOUSE to device stack */
|
|
WorkItem = IoAllocateWorkItem(DeviceObject);
|
|
if (!WorkItem)
|
|
{
|
|
WARN_(I8042PRT, "IoAllocateWorkItem() failed\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto cleanup;
|
|
}
|
|
WorkItemData = ExAllocatePoolWithTag(
|
|
NonPagedPool,
|
|
sizeof(I8042_HOOK_WORKITEM),
|
|
I8042PRT_TAG);
|
|
if (!WorkItemData)
|
|
{
|
|
WARN_(I8042PRT, "ExAllocatePoolWithTag() failed\n");
|
|
Status = STATUS_NO_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
WorkItemData->WorkItem = WorkItem;
|
|
WorkItemData->Irp = Irp;
|
|
|
|
/* Initialize extension */
|
|
DeviceExtension->Common.Type = Mouse;
|
|
Size = DeviceExtension->Common.PortDeviceExtension->Settings.MouseDataQueueSize * sizeof(MOUSE_INPUT_DATA);
|
|
DeviceExtension->MouseBuffer = ExAllocatePoolWithTag(
|
|
NonPagedPool,
|
|
Size,
|
|
I8042PRT_TAG);
|
|
if (!DeviceExtension->MouseBuffer)
|
|
{
|
|
WARN_(I8042PRT, "ExAllocatePoolWithTag() failed\n");
|
|
Status = STATUS_NO_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
RtlZeroMemory(DeviceExtension->MouseBuffer, Size);
|
|
DeviceExtension->MouseAttributes.InputDataQueueLength =
|
|
DeviceExtension->Common.PortDeviceExtension->Settings.MouseDataQueueSize;
|
|
KeInitializeDpc(
|
|
&DeviceExtension->DpcMouse,
|
|
i8042MouDpcRoutine,
|
|
DeviceExtension);
|
|
KeInitializeDpc(
|
|
&DeviceExtension->DpcMouseTimeout,
|
|
i8042DpcRoutineMouseTimeout,
|
|
DeviceExtension);
|
|
KeInitializeTimer(&DeviceExtension->TimerMouseTimeout);
|
|
DeviceExtension->Common.PortDeviceExtension->MouseExtension = DeviceExtension;
|
|
DeviceExtension->Common.PortDeviceExtension->Flags |= MOUSE_CONNECTED;
|
|
|
|
IoMarkIrpPending(Irp);
|
|
DeviceExtension->MouseState = MouseResetting;
|
|
DeviceExtension->MouseResetState = ExpectingReset;
|
|
DeviceExtension->MouseHook.IsrWritePort = i8042MouIsrWritePort;
|
|
DeviceExtension->MouseHook.QueueMousePacket = i8042MouQueuePacket;
|
|
DeviceExtension->MouseHook.CallContext = DeviceExtension;
|
|
IoQueueWorkItem(WorkItem,
|
|
i8042SendHookWorkItem,
|
|
DelayedWorkQueue,
|
|
WorkItemData);
|
|
Status = STATUS_PENDING;
|
|
break;
|
|
|
|
cleanup:
|
|
if (DeviceExtension->MouseBuffer)
|
|
ExFreePoolWithTag(DeviceExtension->MouseBuffer, I8042PRT_TAG);
|
|
if (WorkItem)
|
|
IoFreeWorkItem(WorkItem);
|
|
if (WorkItemData)
|
|
ExFreePoolWithTag(WorkItemData, I8042PRT_TAG);
|
|
break;
|
|
}
|
|
case IOCTL_INTERNAL_MOUSE_DISCONNECT:
|
|
{
|
|
TRACE_(I8042PRT, "IRP_MJ_INTERNAL_DEVICE_CONTROL / IOCTL_INTERNAL_MOUSE_DISCONNECT\n");
|
|
/* MSDN says that operation is to implemented.
|
|
* To implement it, we just have to do:
|
|
* DeviceExtension->MouseData.ClassService = NULL;
|
|
*/
|
|
Status = STATUS_NOT_IMPLEMENTED;
|
|
break;
|
|
}
|
|
case IOCTL_INTERNAL_I8042_HOOK_MOUSE:
|
|
{
|
|
PINTERNAL_I8042_HOOK_MOUSE MouseHook;
|
|
TRACE_(I8042PRT, "IRP_MJ_INTERNAL_DEVICE_CONTROL / IOCTL_INTERNAL_I8042_HOOK_MOUSE\n");
|
|
if (Stack->Parameters.DeviceIoControl.InputBufferLength < sizeof(INTERNAL_I8042_HOOK_MOUSE))
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
MouseHook = (PINTERNAL_I8042_HOOK_MOUSE)Stack->Parameters.DeviceIoControl.Type3InputBuffer;
|
|
|
|
DeviceExtension->MouseHook.Context = MouseHook->Context;
|
|
if (MouseHook->IsrRoutine)
|
|
DeviceExtension->MouseHook.IsrRoutine = MouseHook->IsrRoutine;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
break;
|
|
}
|
|
case IOCTL_INTERNAL_I8042_MOUSE_WRITE_BUFFER:
|
|
{
|
|
DPRINT1("IOCTL_INTERNAL_I8042_MOUSE_WRITE_BUFFER not implemented\n");
|
|
Status = STATUS_NOT_IMPLEMENTED;
|
|
break;
|
|
}
|
|
case IOCTL_INTERNAL_I8042_MOUSE_START_INFORMATION:
|
|
{
|
|
DPRINT1("IOCTL_INTERNAL_I8042_MOUSE_START_INFORMATION not implemented\n");
|
|
Status = STATUS_NOT_IMPLEMENTED;
|
|
break;
|
|
}
|
|
case IOCTL_MOUSE_QUERY_ATTRIBUTES:
|
|
{
|
|
TRACE_(I8042PRT, "IRP_MJ_INTERNAL_DEVICE_CONTROL / IOCTL_MOUSE_QUERY_ATTRIBUTES\n");
|
|
if (Stack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(MOUSE_ATTRIBUTES))
|
|
{
|
|
Status = STATUS_BUFFER_TOO_SMALL;
|
|
break;
|
|
}
|
|
|
|
*(PMOUSE_ATTRIBUTES) Irp->AssociatedIrp.SystemBuffer = DeviceExtension->MouseAttributes;
|
|
Irp->IoStatus.Information = sizeof(MOUSE_ATTRIBUTES);
|
|
Status = STATUS_SUCCESS;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
ERR_(I8042PRT, "IRP_MJ_INTERNAL_DEVICE_CONTROL / unknown ioctl code 0x%lx\n",
|
|
Stack->Parameters.DeviceIoControl.IoControlCode);
|
|
ASSERT(FALSE);
|
|
return ForwardIrpAndForget(DeviceObject, Irp);
|
|
}
|
|
}
|
|
|
|
if (Status != STATUS_PENDING)
|
|
{
|
|
Irp->IoStatus.Status = Status;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
/* Test if packets are taking too long to come in. If they do, we
|
|
* might have gotten out of sync and should just drop what we have.
|
|
*
|
|
* If we want to be totally right, we'd also have to keep a count of
|
|
* errors, and totally reset the mouse after too much of them (can
|
|
* happen if the user is using a KVM switch and an OS on another port
|
|
* resets the mouse, or if the user hotplugs the mouse, or if we're just
|
|
* generally unlucky). Also note the input parsing routine where we
|
|
* drop invalid input packets.
|
|
*/
|
|
static VOID
|
|
i8042MouInputTestTimeout(
|
|
IN PI8042_MOUSE_EXTENSION DeviceExtension)
|
|
{
|
|
ULARGE_INTEGER Now;
|
|
|
|
if (DeviceExtension->MouseState == MouseExpectingACK ||
|
|
DeviceExtension->MouseState == MouseResetting)
|
|
return;
|
|
|
|
Now.QuadPart = KeQueryInterruptTime();
|
|
|
|
if (DeviceExtension->MouseState != MouseIdle) {
|
|
/* Check if the last byte came too long ago */
|
|
if (Now.QuadPart - DeviceExtension->MousePacketStartTime.QuadPart >
|
|
DeviceExtension->Common.PortDeviceExtension->Settings.MouseSynchIn100ns)
|
|
{
|
|
WARN_(I8042PRT, "Mouse input packet timeout\n");
|
|
DeviceExtension->MouseState = MouseIdle;
|
|
}
|
|
}
|
|
|
|
if (DeviceExtension->MouseState == MouseIdle)
|
|
DeviceExtension->MousePacketStartTime.QuadPart = Now.QuadPart;
|
|
}
|
|
|
|
/*
|
|
* 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
|
|
i8042MouCallIsrHook(
|
|
IN PI8042_MOUSE_EXTENSION DeviceExtension,
|
|
IN UCHAR Status,
|
|
IN UCHAR Input,
|
|
OUT PBOOLEAN ToReturn)
|
|
{
|
|
BOOLEAN HookReturn, HookContinue;
|
|
|
|
HookContinue = FALSE;
|
|
|
|
if (DeviceExtension->MouseHook.IsrRoutine)
|
|
{
|
|
HookReturn = DeviceExtension->MouseHook.IsrRoutine(
|
|
DeviceExtension->MouseHook.Context,
|
|
DeviceExtension->MouseBuffer + DeviceExtension->MouseInBuffer,
|
|
&DeviceExtension->Common.PortDeviceExtension->Packet,
|
|
Status,
|
|
&Input,
|
|
&HookContinue,
|
|
&DeviceExtension->MouseState,
|
|
&DeviceExtension->MouseResetState);
|
|
|
|
if (!HookContinue)
|
|
{
|
|
*ToReturn = HookReturn;
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOLEAN
|
|
i8042MouResetIsr(
|
|
IN PI8042_MOUSE_EXTENSION DeviceExtension,
|
|
IN UCHAR Status,
|
|
IN UCHAR Value)
|
|
{
|
|
PPORT_DEVICE_EXTENSION PortDeviceExtension;
|
|
BOOLEAN ToReturn = FALSE;
|
|
|
|
if (i8042MouCallIsrHook(DeviceExtension, Status, Value, &ToReturn))
|
|
return ToReturn;
|
|
|
|
if (MouseIdle == DeviceExtension->MouseState)
|
|
{
|
|
/* Magic packet value that indicates a reset */
|
|
if (0xAA == Value)
|
|
{
|
|
WARN_(I8042PRT, "Hot plugged mouse!\n");
|
|
DeviceExtension->MouseState = MouseResetting;
|
|
DeviceExtension->MouseResetState = ExpectingReset;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
else if (MouseResetting != DeviceExtension->MouseState)
|
|
return FALSE;
|
|
|
|
DeviceExtension->MouseTimeoutState = TimeoutStart;
|
|
PortDeviceExtension = DeviceExtension->Common.PortDeviceExtension;
|
|
|
|
switch ((ULONG)DeviceExtension->MouseResetState)
|
|
{
|
|
case ExpectingReset:
|
|
if (MOUSE_ACK == Value)
|
|
{
|
|
WARN_(I8042PRT, "Dropping extra ACK\n");
|
|
return TRUE;
|
|
}
|
|
|
|
/* First, 0xFF is sent. The mouse is supposed to say AA00 if ok, FC00 if not. */
|
|
if (0xAA == Value)
|
|
{
|
|
DeviceExtension->MouseResetState++;
|
|
}
|
|
else
|
|
{
|
|
PortDeviceExtension->Flags &= ~MOUSE_PRESENT;
|
|
DeviceExtension->MouseState = MouseIdle;
|
|
WARN_(I8042PRT, "Mouse returned bad reset reply: %x (expected aa)\n", Value);
|
|
}
|
|
return TRUE;
|
|
case ExpectingResetId:
|
|
if (MOUSE_ACK == Value)
|
|
{
|
|
WARN_(I8042PRT, "Dropping extra ACK #2\n");
|
|
return TRUE;
|
|
}
|
|
|
|
if (0x00 == Value)
|
|
{
|
|
DeviceExtension->MouseResetState++;
|
|
DeviceExtension->MouseType = GenericPS2;
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF2);
|
|
}
|
|
else
|
|
{
|
|
PortDeviceExtension->Flags &= ~MOUSE_PRESENT;
|
|
DeviceExtension->MouseState = MouseIdle;
|
|
WARN_(I8042PRT, "Mouse returned bad reset reply part two: %x (expected 0)\n", Value);
|
|
}
|
|
return TRUE;
|
|
case ExpectingGetDeviceIdACK:
|
|
if (MOUSE_ACK == Value)
|
|
{
|
|
DeviceExtension->MouseResetState++;
|
|
}
|
|
else if (MOUSE_NACK == Value || MOUSE_ERROR == Value)
|
|
{
|
|
DeviceExtension->MouseResetState++;
|
|
/* Act as if 00 (normal mouse) was received */
|
|
WARN_(I8042PRT, "Mouse doesn't support 0xd2, (returns %x, expected %x), faking\n", Value, MOUSE_ACK);
|
|
i8042MouResetIsr(DeviceExtension, Status, 0);
|
|
}
|
|
return TRUE;
|
|
case ExpectingGetDeviceIdValue:
|
|
switch (Value)
|
|
{
|
|
case 0x02:
|
|
DeviceExtension->MouseAttributes.MouseIdentifier =
|
|
BALLPOINT_I8042_HARDWARE;
|
|
break;
|
|
case 0x03:
|
|
case 0x04:
|
|
DeviceExtension->MouseAttributes.MouseIdentifier =
|
|
WHEELMOUSE_I8042_HARDWARE;
|
|
break;
|
|
default:
|
|
DeviceExtension->MouseAttributes.MouseIdentifier =
|
|
MOUSE_I8042_HARDWARE;
|
|
}
|
|
DeviceExtension->MouseResetState++;
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xE8);
|
|
return TRUE;
|
|
case ExpectingSetResolutionDefaultACK:
|
|
DeviceExtension->MouseResetState++;
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0x00);
|
|
return TRUE;
|
|
case ExpectingSetResolutionDefaultValueACK:
|
|
DeviceExtension->MouseResetState = ExpectingSetScaling1to1ACK;
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xE6);
|
|
return TRUE;
|
|
case ExpectingSetScaling1to1ACK:
|
|
case ExpectingSetScaling1to1ACK2:
|
|
DeviceExtension->MouseResetState++;
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xE6);
|
|
return TRUE;
|
|
case ExpectingSetScaling1to1ACK3:
|
|
DeviceExtension->MouseResetState++;
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xE9);
|
|
return TRUE;
|
|
case ExpectingReadMouseStatusACK:
|
|
DeviceExtension->MouseResetState++;
|
|
return TRUE;
|
|
case ExpectingReadMouseStatusByte1:
|
|
DeviceExtension->MouseLogiBuffer[0] = Value;
|
|
DeviceExtension->MouseResetState++;
|
|
return TRUE;
|
|
case ExpectingReadMouseStatusByte2:
|
|
DeviceExtension->MouseLogiBuffer[1] = Value;
|
|
DeviceExtension->MouseResetState++;
|
|
return TRUE;
|
|
case ExpectingReadMouseStatusByte3:
|
|
DeviceExtension->MouseLogiBuffer[2] = Value;
|
|
/* Now MouseLogiBuffer is a set of info. If the second
|
|
* byte is 0, the mouse didn't understand the magic
|
|
* code. Otherwise, it it a Logitech and the second byte
|
|
* is the number of buttons, bit 7 of the first byte tells
|
|
* if it understands special E7 commands, the rest is an ID.
|
|
*/
|
|
if (DeviceExtension->MouseLogiBuffer[1])
|
|
{
|
|
DeviceExtension->MouseAttributes.NumberOfButtons =
|
|
DeviceExtension->MouseLogiBuffer[1];
|
|
DeviceExtension->MouseType = Ps2pp;
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF3);
|
|
DeviceExtension->MouseResetState = ExpectingSetSamplingRateACK;
|
|
/* TODO: Go through EnableWheel and Enable5Buttons */
|
|
return TRUE;
|
|
}
|
|
DeviceExtension->MouseResetState = EnableWheel;
|
|
i8042MouResetIsr(DeviceExtension, Status, Value);
|
|
return TRUE;
|
|
case EnableWheel:
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF3);
|
|
DeviceExtension->MouseResetState = 1001;
|
|
return TRUE;
|
|
case 1001:
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xC8);
|
|
DeviceExtension->MouseResetState++;
|
|
return TRUE;
|
|
case 1002:
|
|
case 1004:
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF3);
|
|
DeviceExtension->MouseResetState++;
|
|
return TRUE;
|
|
case 1003:
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0x64);
|
|
DeviceExtension->MouseResetState++;
|
|
return TRUE;
|
|
case 1005:
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0x50);
|
|
DeviceExtension->MouseResetState++;
|
|
return TRUE;
|
|
case 1006:
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF2);
|
|
DeviceExtension->MouseResetState++;
|
|
return TRUE;
|
|
case 1007:
|
|
/* Ignore ACK */
|
|
DeviceExtension->MouseResetState++;
|
|
return TRUE;
|
|
case 1008:
|
|
if (0x03 == Value) {
|
|
/* It's either an Intellimouse or Intellimouse Explorer. */
|
|
DeviceExtension->MouseAttributes.NumberOfButtons = 3;
|
|
DeviceExtension->MouseAttributes.MouseIdentifier =
|
|
WHEELMOUSE_I8042_HARDWARE;
|
|
DeviceExtension->MouseType = Intellimouse;
|
|
DeviceExtension->MouseResetState = Enable5Buttons;
|
|
i8042MouResetIsr(DeviceExtension, Status, Value);
|
|
}
|
|
else
|
|
{
|
|
/* Just set the default settings and be done */
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF3);
|
|
DeviceExtension->MouseResetState = ExpectingSetSamplingRateACK;
|
|
}
|
|
return TRUE;
|
|
case Enable5Buttons:
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF3);
|
|
DeviceExtension->MouseResetState = 1021;
|
|
return TRUE;
|
|
case 1022:
|
|
case 1024:
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF3);
|
|
DeviceExtension->MouseResetState++;
|
|
return TRUE;
|
|
case 1021:
|
|
case 1023:
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xC8);
|
|
DeviceExtension->MouseResetState++;
|
|
return TRUE;
|
|
case 1025:
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0x50);
|
|
DeviceExtension->MouseResetState++;
|
|
return TRUE;
|
|
case 1026:
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF2);
|
|
DeviceExtension->MouseResetState++;
|
|
return TRUE;
|
|
case 1027:
|
|
if (0x04 == Value)
|
|
{
|
|
DeviceExtension->MouseAttributes.NumberOfButtons = 5;
|
|
DeviceExtension->MouseAttributes.MouseIdentifier =
|
|
WHEELMOUSE_I8042_HARDWARE;
|
|
DeviceExtension->MouseType = IntellimouseExplorer;
|
|
}
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF3);
|
|
DeviceExtension->MouseResetState = ExpectingSetSamplingRateACK;
|
|
return TRUE;
|
|
case ExpectingSetSamplingRateACK:
|
|
DeviceExtension->MouseHook.IsrWritePort(
|
|
DeviceExtension->MouseHook.CallContext,
|
|
(UCHAR)DeviceExtension->MouseAttributes.SampleRate);
|
|
DeviceExtension->MouseResetState++;
|
|
return TRUE;
|
|
case ExpectingSetSamplingRateValueACK:
|
|
if (MOUSE_NACK == Value)
|
|
{
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0x3C);
|
|
DeviceExtension->MouseAttributes.SampleRate = (USHORT)PortDeviceExtension->Settings.SampleRate;
|
|
DeviceExtension->MouseResetState = 1040;
|
|
return TRUE;
|
|
}
|
|
case 1040: /* Fallthrough */
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xE8);
|
|
DeviceExtension->MouseResetState = ExpectingFinalResolutionACK;
|
|
return TRUE;
|
|
case ExpectingFinalResolutionACK:
|
|
DeviceExtension->MouseHook.IsrWritePort(
|
|
DeviceExtension->MouseHook.CallContext,
|
|
(UCHAR)(PortDeviceExtension->Settings.MouseResolution & 0xff));
|
|
INFO_(I8042PRT, "Mouse resolution %lu\n",
|
|
PortDeviceExtension->Settings.MouseResolution);
|
|
DeviceExtension->MouseResetState = ExpectingFinalResolutionValueACK;
|
|
return TRUE;
|
|
case ExpectingFinalResolutionValueACK:
|
|
DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF4);
|
|
DeviceExtension->MouseResetState = ExpectingEnableACK;
|
|
return TRUE;
|
|
case ExpectingEnableACK:
|
|
PortDeviceExtension->Flags |= MOUSE_PRESENT;
|
|
DeviceExtension->MouseState = MouseIdle;
|
|
DeviceExtension->MouseTimeoutState = TimeoutCancel;
|
|
INFO_(I8042PRT, "Mouse type = %u\n", DeviceExtension->MouseType);
|
|
return TRUE;
|
|
default:
|
|
if (DeviceExtension->MouseResetState < 100 || DeviceExtension->MouseResetState > 999)
|
|
ERR_(I8042PRT, "MouseResetState went out of range: %lu\n", DeviceExtension->MouseResetState);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
BOOLEAN NTAPI
|
|
i8042MouInterruptService(
|
|
IN PKINTERRUPT Interrupt,
|
|
PVOID Context)
|
|
{
|
|
PI8042_MOUSE_EXTENSION DeviceExtension;
|
|
PPORT_DEVICE_EXTENSION PortDeviceExtension;
|
|
ULONG Counter;
|
|
UCHAR Output = 0, PortStatus = 0;
|
|
NTSTATUS Status;
|
|
|
|
UNREFERENCED_PARAMETER(Interrupt);
|
|
|
|
__analysis_assume(Context != NULL);
|
|
DeviceExtension = Context;
|
|
PortDeviceExtension = DeviceExtension->Common.PortDeviceExtension;
|
|
Counter = PortDeviceExtension->Settings.PollStatusIterations;
|
|
|
|
while (Counter)
|
|
{
|
|
Status = i8042ReadStatus(PortDeviceExtension, &PortStatus);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
WARN_(I8042PRT, "i8042ReadStatus() failed with status 0x%08lx\n", Status);
|
|
return FALSE;
|
|
}
|
|
Status = i8042ReadMouseData(PortDeviceExtension, &Output);
|
|
if (NT_SUCCESS(Status))
|
|
break;
|
|
KeStallExecutionProcessor(1);
|
|
Counter--;
|
|
}
|
|
if (Counter == 0)
|
|
{
|
|
WARN_(I8042PRT, "Spurious i8042 mouse interrupt\n");
|
|
return FALSE;
|
|
}
|
|
|
|
INFO_(I8042PRT, "Got: 0x%02x\n", Output);
|
|
|
|
if (i8042PacketIsr(PortDeviceExtension, Output))
|
|
{
|
|
if (PortDeviceExtension->PacketComplete)
|
|
{
|
|
TRACE_(I8042PRT, "Packet complete\n");
|
|
KeInsertQueueDpc(&DeviceExtension->DpcMouse, NULL, NULL);
|
|
}
|
|
TRACE_(I8042PRT, "Irq eaten by packet\n");
|
|
return TRUE;
|
|
}
|
|
|
|
TRACE_(I8042PRT, "Irq is mouse input\n");
|
|
|
|
i8042MouInputTestTimeout(DeviceExtension);
|
|
|
|
if (i8042MouResetIsr(DeviceExtension, PortStatus, Output))
|
|
{
|
|
TRACE_(I8042PRT, "Handled by ResetIsr or hooked Isr\n");
|
|
if (NoChange != DeviceExtension->MouseTimeoutState) {
|
|
KeInsertQueueDpc(&DeviceExtension->DpcMouse, NULL, NULL);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
if (DeviceExtension->MouseType == Ps2pp)
|
|
i8042MouHandlePs2pp(DeviceExtension, Output);
|
|
else
|
|
i8042MouHandle(DeviceExtension, Output);
|
|
|
|
return TRUE;
|
|
}
|