xref: /reactos/drivers/input/i8042prt/mouse.c (revision c2c66aff)
1 /*
2  * PROJECT:     ReactOS i8042 (ps/2 keyboard-mouse controller) driver
3  * LICENSE:     GPL - See COPYING in the top level directory
4  * FILE:        drivers/input/i8042prt/mouse.c
5  * PURPOSE:     Mouse specific functions
6  * PROGRAMMERS: Copyright Victor Kirhenshtein (sauros@iname.com)
7                 Copyright Jason Filby (jasonfilby@yahoo.com)
8                 Copyright Martijn Vernooij (o112w8r02@sneakemail.com)
9                 Copyright 2006-2007 Herv� Poussineau (hpoussin@reactos.org)
10                 Copyright 2008 Colin Finck (mail@colinfinck.de)
11  */
12 
13 /* INCLUDES ****************************************************************/
14 
15 #include "i8042prt.h"
16 
17 #include <debug.h>
18 
19 /* FUNCTIONS *****************************************************************/
20 
21 static KDEFERRED_ROUTINE i8042MouDpcRoutine;
22 static KDEFERRED_ROUTINE i8042DpcRoutineMouseTimeout;
23 
24 /*
25  * These functions are callbacks for filter driver custom interrupt
26  * service routines.
27  */
28 static VOID NTAPI
i8042MouIsrWritePort(IN PVOID Context,IN UCHAR Value)29 i8042MouIsrWritePort(
30 	IN PVOID Context,
31 	IN UCHAR Value)
32 {
33 	PI8042_MOUSE_EXTENSION DeviceExtension;
34 
35 	DeviceExtension = (PI8042_MOUSE_EXTENSION)Context;
36 
37 	if (DeviceExtension->MouseHook.IsrWritePort != i8042MouIsrWritePort)
38 	{
39 		DeviceExtension->MouseHook.IsrWritePort(
40 			DeviceExtension->MouseHook.CallContext,
41 			Value);
42 	}
43 	else
44 		i8042IsrWritePort(DeviceExtension->Common.PortDeviceExtension, Value, CTRL_WRITE_MOUSE);
45 }
46 
47 static VOID NTAPI
i8042MouQueuePacket(IN PVOID Context)48 i8042MouQueuePacket(
49 	IN PVOID Context)
50 {
51 	PI8042_MOUSE_EXTENSION DeviceExtension;
52 
53 	DeviceExtension = (PI8042_MOUSE_EXTENSION)Context;
54 
55 	DeviceExtension->MouseComplete = TRUE;
56 	DeviceExtension->MouseInBuffer++;
57 	if (DeviceExtension->MouseInBuffer >= DeviceExtension->Common.PortDeviceExtension->Settings.MouseDataQueueSize)
58 	{
59 		WARN_(I8042PRT, "Mouse buffer overflow\n");
60 		DeviceExtension->MouseInBuffer--;
61 	}
62 
63 	TRACE_(I8042PRT, "Irq completes mouse packet\n");
64 	KeInsertQueueDpc(&DeviceExtension->DpcMouse, NULL, NULL);
65 }
66 
67 VOID
i8042MouHandle(IN PI8042_MOUSE_EXTENSION DeviceExtension,IN UCHAR Output)68 i8042MouHandle(
69 	IN PI8042_MOUSE_EXTENSION DeviceExtension,
70 	IN UCHAR Output)
71 {
72 	PMOUSE_INPUT_DATA MouseInput;
73 	CHAR Scroll;
74 
75 	MouseInput = DeviceExtension->MouseBuffer + DeviceExtension->MouseInBuffer;
76 
77 	switch (DeviceExtension->MouseState)
78 	{
79 		case MouseIdle:
80 			/* This bit should be 1, if not drop the packet, we
81 			 * might be lucky and get in sync again
82 			 */
83 			if (!(Output & 8)) {
84 				WARN_(I8042PRT, "Bad input, dropping..\n");
85 				return;
86 			}
87 
88 			MouseInput->Buttons = 0;
89 			MouseInput->RawButtons = 0;
90 			MouseInput->Flags = MOUSE_MOVE_RELATIVE;
91 
92 			/* Note how we ignore the overflow bits, like Windows
93 			 * is said to do. There's no reasonable thing to do
94 			 * anyway.
95 			 */
96 
97 			if (Output & 16)
98 				MouseInput->LastX = 1;
99 			else
100 				MouseInput->LastX = 0;
101 			if (Output & 32)
102 				MouseInput->LastY = 1;
103 			else
104 				MouseInput->LastY = 0;
105 
106 			if (Output & 1)
107 				MouseInput->RawButtons |= MOUSE_LEFT_BUTTON_DOWN;
108 			if (Output & 2)
109 				MouseInput->RawButtons |= MOUSE_RIGHT_BUTTON_DOWN;
110 			if (Output & 4)
111 				MouseInput->RawButtons |= MOUSE_MIDDLE_BUTTON_DOWN;
112 
113 			DeviceExtension->MouseState = XMovement;
114 			break;
115 
116 		case XMovement:
117 			if (MouseInput->LastX)
118 				MouseInput->LastX = (LONG) Output - 256;
119 			else
120 				MouseInput->LastX = Output;
121 
122 			DeviceExtension->MouseState = YMovement;
123 			break;
124 
125 		case YMovement:
126 			if (MouseInput->LastY)
127 				MouseInput->LastY = (LONG)Output - 256;
128 			else
129 				MouseInput->LastY = (LONG)Output;
130 
131 			/* Windows wants it the other way around */
132 			MouseInput->LastY = -MouseInput->LastY;
133 
134 			if (DeviceExtension->MouseType == GenericPS2 ||
135 			    DeviceExtension->MouseType == Ps2pp)
136 			{
137 				i8042MouHandleButtons(
138 					DeviceExtension,
139 					MOUSE_LEFT_BUTTON_DOWN |
140 					MOUSE_RIGHT_BUTTON_DOWN |
141 					MOUSE_MIDDLE_BUTTON_DOWN);
142 				DeviceExtension->MouseHook.QueueMousePacket(DeviceExtension->MouseHook.CallContext);
143 				DeviceExtension->MouseState = MouseIdle;
144 			}
145 			else
146 			{
147 				DeviceExtension->MouseState = ZMovement;
148 			}
149 			break;
150 
151 		case ZMovement:
152 			Scroll = Output & 0x0f;
153 			if (Scroll & 8)
154 				Scroll |= 0xf0;
155 
156 			if (Scroll)
157 			{
158 				MouseInput->RawButtons |= MOUSE_WHEEL;
159 				MouseInput->ButtonData = (USHORT)(Scroll * -WHEEL_DELTA);
160 			}
161 
162 			if (DeviceExtension->MouseType == IntellimouseExplorer)
163 			{
164 				if (Output & 16)
165 					MouseInput->RawButtons |= MOUSE_BUTTON_4_DOWN;
166 				if (Output & 32)
167 					MouseInput->RawButtons |= MOUSE_BUTTON_5_DOWN;
168 			}
169 			i8042MouHandleButtons(
170 				DeviceExtension,
171 				MOUSE_LEFT_BUTTON_DOWN |
172 				MOUSE_RIGHT_BUTTON_DOWN |
173 				MOUSE_MIDDLE_BUTTON_DOWN |
174 				MOUSE_BUTTON_4_DOWN |
175 				MOUSE_BUTTON_5_DOWN);
176 			DeviceExtension->MouseHook.QueueMousePacket(DeviceExtension->MouseHook.CallContext);
177 			DeviceExtension->MouseState = MouseIdle;
178 			break;
179 
180 		default:
181 			ERR_(I8042PRT, "Unexpected state 0x%lx!\n", DeviceExtension->MouseState);
182 			ASSERT(FALSE);
183 	}
184 }
185 
186 /*
187  * Updates ButtonFlags according to RawButtons and a saved state;
188  * Only takes in account the bits that are set in Mask
189  */
190 VOID
i8042MouHandleButtons(IN PI8042_MOUSE_EXTENSION DeviceExtension,IN USHORT Mask)191 i8042MouHandleButtons(
192 	IN PI8042_MOUSE_EXTENSION DeviceExtension,
193 	IN USHORT Mask)
194 {
195 	PMOUSE_INPUT_DATA MouseInput;
196 	USHORT NewButtonData;
197 	USHORT ButtonDiff;
198 
199 	MouseInput = DeviceExtension->MouseBuffer + DeviceExtension->MouseInBuffer;
200 	NewButtonData = (USHORT)(MouseInput->RawButtons & Mask);
201 	ButtonDiff = (NewButtonData ^ DeviceExtension->MouseButtonState) & Mask;
202 
203 	/* Note that the defines are such:
204 	 * MOUSE_LEFT_BUTTON_DOWN 1
205 	 * MOUSE_LEFT_BUTTON_UP   2
206 	 */
207 	MouseInput->ButtonFlags |= (NewButtonData & ButtonDiff) |
208 		(((~(NewButtonData)) << 1) & (ButtonDiff << 1)) |
209 		(MouseInput->RawButtons & 0xfc00);
210 
211 	INFO_(I8042PRT, "Left raw/up/down: %u/%u/%u\n",
212 		MouseInput->RawButtons & MOUSE_LEFT_BUTTON_DOWN,
213 		MouseInput->ButtonFlags & MOUSE_LEFT_BUTTON_DOWN,
214 		MouseInput->ButtonFlags & MOUSE_LEFT_BUTTON_UP);
215 
216 	DeviceExtension->MouseButtonState =
217 		(DeviceExtension->MouseButtonState & ~Mask) | (NewButtonData & Mask);
218 }
219 
220 /* Does final initializations for the mouse. This method
221  * is called just before connecting the interrupt.
222  */
223 NTSTATUS
i8042MouInitialize(IN PI8042_MOUSE_EXTENSION DeviceExtension)224 i8042MouInitialize(
225 	IN PI8042_MOUSE_EXTENSION DeviceExtension)
226 {
227 	NTSTATUS Status;
228 	UCHAR Value;
229 
230 	/* Enable the PS/2 mouse port */
231 	i8042Write(DeviceExtension->Common.PortDeviceExtension, DeviceExtension->Common.PortDeviceExtension->ControlPort, MOUSE_ENAB);
232 
233 	/* Enable the mouse */
234 	if(!i8042IsrWritePort(DeviceExtension->Common.PortDeviceExtension, MOU_ENAB, CTRL_WRITE_MOUSE))
235 	{
236 		WARN_(I8042PRT, "Failed to enable mouse!\n");
237 		return STATUS_IO_DEVICE_ERROR;
238 	}
239 
240 	Status = i8042ReadDataWait(DeviceExtension->Common.PortDeviceExtension, &Value);
241 	if (!NT_SUCCESS(Status))
242 	{
243 		WARN_(I8042PRT, "Failed to read the response of MOU_ENAB, status 0x%08lx\n", Status);
244 		return Status;
245 	}
246 
247 	if(Value == MOUSE_ACK)
248 	{
249 		INFO_(I8042PRT, "Mouse was enabled successfully!\n");
250 		return STATUS_SUCCESS;
251 	}
252 
253 	WARN_(I8042PRT, "Got 0x%02x instead of 0xFA\n", Value);
254 	return STATUS_IO_DEVICE_ERROR;
255 }
256 
257 static VOID NTAPI
i8042MouDpcRoutine(IN PKDPC Dpc,IN PVOID DeferredContext,IN PVOID SystemArgument1,IN PVOID SystemArgument2)258 i8042MouDpcRoutine(
259 	IN PKDPC Dpc,
260 	IN PVOID DeferredContext,
261 	IN PVOID SystemArgument1,
262 	IN PVOID SystemArgument2)
263 {
264 	PI8042_MOUSE_EXTENSION DeviceExtension;
265 	PPORT_DEVICE_EXTENSION PortDeviceExtension;
266 	ULONG MouseTransferred = 0;
267 	ULONG MouseInBufferCopy;
268 	KIRQL Irql;
269 	LARGE_INTEGER Timeout;
270 
271 	UNREFERENCED_PARAMETER(Dpc);
272 	UNREFERENCED_PARAMETER(SystemArgument1);
273 	UNREFERENCED_PARAMETER(SystemArgument2);
274 
275 	__analysis_assume(DeferredContext != NULL);
276 	DeviceExtension = DeferredContext;
277 	PortDeviceExtension = DeviceExtension->Common.PortDeviceExtension;
278 
279 	switch (DeviceExtension->MouseTimeoutState)
280 	{
281 		case TimeoutStart:
282 		{
283 			DeviceExtension->MouseTimeoutState = NoChange;
284 			if (DeviceExtension->MouseTimeoutActive &&
285 			    !KeCancelTimer(&DeviceExtension->TimerMouseTimeout))
286 			{
287 				/* The timer fired already, give up */
288 				DeviceExtension->MouseTimeoutActive = FALSE;
289 				return;
290 			}
291 
292 			Timeout.QuadPart = -15000000; /* 1.5 seconds, should be enough */
293 
294 			KeSetTimer(
295 				&DeviceExtension->TimerMouseTimeout,
296 				Timeout,
297 				&DeviceExtension->DpcMouseTimeout);
298 			DeviceExtension->MouseTimeoutActive = TRUE;
299 			return;
300 		}
301 
302 		case TimeoutCancel:
303 		{
304 			DeviceExtension->MouseTimeoutState = NoChange;
305 			KeCancelTimer(&DeviceExtension->TimerMouseTimeout);
306 			DeviceExtension->MouseTimeoutActive = FALSE;
307 		}
308 
309 		default:
310 			;/* nothing, don't want a warning */
311 	}
312 
313 	/* Should be unlikely */
314 	if (!DeviceExtension->MouseComplete)
315 		return;
316 
317 	Irql = KeAcquireInterruptSpinLock(PortDeviceExtension->HighestDIRQLInterrupt);
318 
319 	DeviceExtension->MouseComplete = FALSE;
320 	MouseInBufferCopy = DeviceExtension->MouseInBuffer;
321 
322 	KeReleaseInterruptSpinLock(PortDeviceExtension->HighestDIRQLInterrupt, Irql);
323 
324 	TRACE_(I8042PRT, "Send a mouse packet\n");
325 
326 	if (!DeviceExtension->MouseData.ClassService)
327 		return;
328 
329 	INFO_(I8042PRT, "Sending %lu mouse move(s)\n", MouseInBufferCopy);
330 	(*(PSERVICE_CALLBACK_ROUTINE)DeviceExtension->MouseData.ClassService)(
331 		DeviceExtension->MouseData.ClassDeviceObject,
332 		DeviceExtension->MouseBuffer,
333 		DeviceExtension->MouseBuffer + MouseInBufferCopy,
334 		&MouseTransferred);
335 
336 	Irql = KeAcquireInterruptSpinLock(PortDeviceExtension->HighestDIRQLInterrupt);
337 	DeviceExtension->MouseInBuffer -= MouseTransferred;
338 	if (DeviceExtension->MouseInBuffer)
339 		RtlMoveMemory(
340 			DeviceExtension->MouseBuffer,
341 			DeviceExtension->MouseBuffer + MouseTransferred,
342 			DeviceExtension->MouseInBuffer * sizeof(MOUSE_INPUT_DATA));
343 	KeReleaseInterruptSpinLock(PortDeviceExtension->HighestDIRQLInterrupt, Irql);
344 }
345 
346 /* This timer DPC will be called when the mouse reset times out.
347  * I'll just send the 'disable mouse port' command to the controller
348  * and say the mouse doesn't exist.
349  */
350 static VOID NTAPI
i8042DpcRoutineMouseTimeout(IN PKDPC Dpc,IN PVOID DeferredContext,IN PVOID SystemArgument1,IN PVOID SystemArgument2)351 i8042DpcRoutineMouseTimeout(
352 	IN PKDPC Dpc,
353 	IN PVOID DeferredContext,
354 	IN PVOID SystemArgument1,
355 	IN PVOID SystemArgument2)
356 {
357 	PI8042_MOUSE_EXTENSION DeviceExtension;
358 	PPORT_DEVICE_EXTENSION PortDeviceExtension;
359 	KIRQL Irql;
360 
361 	UNREFERENCED_PARAMETER(Dpc);
362 	UNREFERENCED_PARAMETER(SystemArgument1);
363 	UNREFERENCED_PARAMETER(SystemArgument2);
364 
365 	__analysis_assume(DeferredContext != NULL);
366 	DeviceExtension = DeferredContext;
367 	PortDeviceExtension = DeviceExtension->Common.PortDeviceExtension;
368 
369 	Irql = KeAcquireInterruptSpinLock(PortDeviceExtension->HighestDIRQLInterrupt);
370 
371 	WARN_(I8042PRT, "Mouse initialization timeout! (substate %x)\n",
372 		DeviceExtension->MouseResetState);
373 
374 	PortDeviceExtension->Flags &= ~MOUSE_PRESENT;
375 
376 	KeReleaseInterruptSpinLock(PortDeviceExtension->HighestDIRQLInterrupt, Irql);
377 }
378 
379 /*
380  * Runs the mouse IOCTL_INTERNAL dispatch.
381  */
382 NTSTATUS NTAPI
i8042MouInternalDeviceControl(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)383 i8042MouInternalDeviceControl(
384 	IN PDEVICE_OBJECT DeviceObject,
385 	IN PIRP Irp)
386 {
387 	PIO_STACK_LOCATION Stack;
388 	PI8042_MOUSE_EXTENSION DeviceExtension;
389 	NTSTATUS Status;
390 
391 	Stack = IoGetCurrentIrpStackLocation(Irp);
392 	Irp->IoStatus.Information = 0;
393 	DeviceExtension = (PI8042_MOUSE_EXTENSION)DeviceObject->DeviceExtension;
394 
395 	switch (Stack->Parameters.DeviceIoControl.IoControlCode)
396 	{
397 		case IOCTL_INTERNAL_MOUSE_CONNECT:
398 		{
399 			SIZE_T Size;
400 			PIO_WORKITEM WorkItem = NULL;
401 			PI8042_HOOK_WORKITEM WorkItemData = NULL;
402 
403 			TRACE_(I8042PRT, "IRP_MJ_INTERNAL_DEVICE_CONTROL / IOCTL_INTERNAL_MOUSE_CONNECT\n");
404 			if (Stack->Parameters.DeviceIoControl.InputBufferLength != sizeof(CONNECT_DATA))
405 			{
406 				Status = STATUS_INVALID_PARAMETER;
407 				goto cleanup;
408 			}
409 
410 			DeviceExtension->MouseData =
411 				*((PCONNECT_DATA)Stack->Parameters.DeviceIoControl.Type3InputBuffer);
412 
413 			/* Send IOCTL_INTERNAL_I8042_HOOK_MOUSE to device stack */
414 			WorkItem = IoAllocateWorkItem(DeviceObject);
415 			if (!WorkItem)
416 			{
417 				WARN_(I8042PRT, "IoAllocateWorkItem() failed\n");
418 				Status = STATUS_INSUFFICIENT_RESOURCES;
419 				goto cleanup;
420 			}
421 			WorkItemData = ExAllocatePoolWithTag(
422 				NonPagedPool,
423 				sizeof(I8042_HOOK_WORKITEM),
424 				I8042PRT_TAG);
425 			if (!WorkItemData)
426 			{
427 				WARN_(I8042PRT, "ExAllocatePoolWithTag() failed\n");
428 				Status = STATUS_NO_MEMORY;
429 				goto cleanup;
430 			}
431 			WorkItemData->WorkItem = WorkItem;
432 			WorkItemData->Irp = Irp;
433 
434 			/* Initialize extension */
435 			DeviceExtension->Common.Type = Mouse;
436 			Size = DeviceExtension->Common.PortDeviceExtension->Settings.MouseDataQueueSize * sizeof(MOUSE_INPUT_DATA);
437 			DeviceExtension->MouseBuffer = ExAllocatePoolWithTag(
438 				NonPagedPool,
439 				Size,
440 				I8042PRT_TAG);
441 			if (!DeviceExtension->MouseBuffer)
442 			{
443 				WARN_(I8042PRT, "ExAllocatePoolWithTag() failed\n");
444 				Status = STATUS_NO_MEMORY;
445 				goto cleanup;
446 			}
447 			RtlZeroMemory(DeviceExtension->MouseBuffer, Size);
448 			DeviceExtension->MouseAttributes.InputDataQueueLength =
449 				DeviceExtension->Common.PortDeviceExtension->Settings.MouseDataQueueSize;
450 			KeInitializeDpc(
451 				&DeviceExtension->DpcMouse,
452 				i8042MouDpcRoutine,
453 				DeviceExtension);
454 			KeInitializeDpc(
455 				&DeviceExtension->DpcMouseTimeout,
456 				i8042DpcRoutineMouseTimeout,
457 				DeviceExtension);
458 			KeInitializeTimer(&DeviceExtension->TimerMouseTimeout);
459 			DeviceExtension->Common.PortDeviceExtension->MouseExtension = DeviceExtension;
460 			DeviceExtension->Common.PortDeviceExtension->Flags |= MOUSE_CONNECTED;
461 
462 			IoMarkIrpPending(Irp);
463 			DeviceExtension->MouseState = MouseResetting;
464 			DeviceExtension->MouseResetState = ExpectingReset;
465 			DeviceExtension->MouseHook.IsrWritePort = i8042MouIsrWritePort;
466 			DeviceExtension->MouseHook.QueueMousePacket = i8042MouQueuePacket;
467 			DeviceExtension->MouseHook.CallContext = DeviceExtension;
468 			IoQueueWorkItem(WorkItem,
469 				i8042SendHookWorkItem,
470 				DelayedWorkQueue,
471 				WorkItemData);
472 			Status = STATUS_PENDING;
473 			break;
474 
475 cleanup:
476 			if (DeviceExtension->MouseBuffer)
477 				ExFreePoolWithTag(DeviceExtension->MouseBuffer, I8042PRT_TAG);
478 			if (WorkItem)
479 				IoFreeWorkItem(WorkItem);
480 			if (WorkItemData)
481 				ExFreePoolWithTag(WorkItemData, I8042PRT_TAG);
482 			break;
483 		}
484 		case IOCTL_INTERNAL_MOUSE_DISCONNECT:
485 		{
486 			TRACE_(I8042PRT, "IRP_MJ_INTERNAL_DEVICE_CONTROL / IOCTL_INTERNAL_MOUSE_DISCONNECT\n");
487 			/* MSDN says that operation is to implemented.
488 			 * To implement it, we just have to do:
489 			 * DeviceExtension->MouseData.ClassService = NULL;
490 			 */
491 			Status = STATUS_NOT_IMPLEMENTED;
492 			break;
493 		}
494 		case IOCTL_INTERNAL_I8042_HOOK_MOUSE:
495 		{
496 			PINTERNAL_I8042_HOOK_MOUSE MouseHook;
497 			TRACE_(I8042PRT, "IRP_MJ_INTERNAL_DEVICE_CONTROL / IOCTL_INTERNAL_I8042_HOOK_MOUSE\n");
498 			if (Stack->Parameters.DeviceIoControl.InputBufferLength < sizeof(INTERNAL_I8042_HOOK_MOUSE))
499 			{
500 				Status = STATUS_INVALID_PARAMETER;
501 				break;
502 			}
503 			MouseHook = (PINTERNAL_I8042_HOOK_MOUSE)Stack->Parameters.DeviceIoControl.Type3InputBuffer;
504 
505 			DeviceExtension->MouseHook.Context = MouseHook->Context;
506 			if (MouseHook->IsrRoutine)
507 				DeviceExtension->MouseHook.IsrRoutine = MouseHook->IsrRoutine;
508 
509 			Status = STATUS_SUCCESS;
510 			break;
511 		}
512 		case IOCTL_INTERNAL_I8042_MOUSE_WRITE_BUFFER:
513 		{
514 			DPRINT1("IOCTL_INTERNAL_I8042_MOUSE_WRITE_BUFFER not implemented\n");
515 			Status = STATUS_NOT_IMPLEMENTED;
516 			break;
517 		}
518 		case IOCTL_INTERNAL_I8042_MOUSE_START_INFORMATION:
519 		{
520 			DPRINT1("IOCTL_INTERNAL_I8042_MOUSE_START_INFORMATION not implemented\n");
521 			Status = STATUS_NOT_IMPLEMENTED;
522 			break;
523 		}
524 		case IOCTL_MOUSE_QUERY_ATTRIBUTES:
525 		{
526 			TRACE_(I8042PRT, "IRP_MJ_INTERNAL_DEVICE_CONTROL / IOCTL_MOUSE_QUERY_ATTRIBUTES\n");
527 			if (Stack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(MOUSE_ATTRIBUTES))
528 			{
529 				Status = STATUS_BUFFER_TOO_SMALL;
530 				break;
531 			}
532 
533 			*(PMOUSE_ATTRIBUTES) Irp->AssociatedIrp.SystemBuffer = DeviceExtension->MouseAttributes;
534 			Irp->IoStatus.Information = sizeof(MOUSE_ATTRIBUTES);
535 			Status = STATUS_SUCCESS;
536 			break;
537 		}
538 		default:
539 		{
540 			ERR_(I8042PRT, "IRP_MJ_INTERNAL_DEVICE_CONTROL / unknown ioctl code 0x%lx\n",
541 				Stack->Parameters.DeviceIoControl.IoControlCode);
542 			ASSERT(FALSE);
543 			return ForwardIrpAndForget(DeviceObject, Irp);
544 		}
545 	}
546 
547 	if (Status != STATUS_PENDING)
548 	{
549 		Irp->IoStatus.Status = Status;
550 		IoCompleteRequest(Irp, IO_NO_INCREMENT);
551 	}
552 	return Status;
553 }
554 
555 /* Test if packets are taking too long to come in. If they do, we
556  * might have gotten out of sync and should just drop what we have.
557  *
558  * If we want to be totally right, we'd also have to keep a count of
559  * errors, and totally reset the mouse after too much of them (can
560  * happen if the user is using a KVM switch and an OS on another port
561  * resets the mouse, or if the user hotplugs the mouse, or if we're just
562  * generally unlucky). Also note the input parsing routine where we
563  * drop invalid input packets.
564  */
565 static VOID
i8042MouInputTestTimeout(IN PI8042_MOUSE_EXTENSION DeviceExtension)566 i8042MouInputTestTimeout(
567 	IN PI8042_MOUSE_EXTENSION DeviceExtension)
568 {
569 	ULARGE_INTEGER Now;
570 
571 	if (DeviceExtension->MouseState == MouseExpectingACK ||
572 	    DeviceExtension->MouseState == MouseResetting)
573 		return;
574 
575 	Now.QuadPart = KeQueryInterruptTime();
576 
577 	if (DeviceExtension->MouseState != MouseIdle) {
578 		/* Check if the last byte came too long ago */
579 		if (Now.QuadPart - DeviceExtension->MousePacketStartTime.QuadPart >
580 		    DeviceExtension->Common.PortDeviceExtension->Settings.MouseSynchIn100ns)
581 		{
582 			WARN_(I8042PRT, "Mouse input packet timeout\n");
583 			DeviceExtension->MouseState = MouseIdle;
584 		}
585 	}
586 
587 	if (DeviceExtension->MouseState == MouseIdle)
588 		DeviceExtension->MousePacketStartTime.QuadPart = Now.QuadPart;
589 }
590 
591 /*
592  * Call the customization hook. The ToReturn parameter is about wether
593  * we should go on with the interrupt. The return value is what
594  * we should return (indicating to the system wether someone else
595  * should try to handle the interrupt)
596  */
597 static BOOLEAN
i8042MouCallIsrHook(IN PI8042_MOUSE_EXTENSION DeviceExtension,IN UCHAR Status,IN UCHAR Input,OUT PBOOLEAN ToReturn)598 i8042MouCallIsrHook(
599 	IN PI8042_MOUSE_EXTENSION DeviceExtension,
600 	IN UCHAR Status,
601 	IN UCHAR Input,
602 	OUT PBOOLEAN ToReturn)
603 {
604 	BOOLEAN HookReturn, HookContinue;
605 
606 	HookContinue = FALSE;
607 
608 	if (DeviceExtension->MouseHook.IsrRoutine)
609 	{
610 		HookReturn = DeviceExtension->MouseHook.IsrRoutine(
611 			DeviceExtension->MouseHook.Context,
612 			DeviceExtension->MouseBuffer + DeviceExtension->MouseInBuffer,
613 			&DeviceExtension->Common.PortDeviceExtension->Packet,
614 			Status,
615 			&Input,
616 			&HookContinue,
617 			&DeviceExtension->MouseState,
618 			&DeviceExtension->MouseResetState);
619 
620 		if (!HookContinue)
621 		{
622 			*ToReturn = HookReturn;
623 			return TRUE;
624 		}
625 	}
626 	return FALSE;
627 }
628 
629 static BOOLEAN
i8042MouResetIsr(IN PI8042_MOUSE_EXTENSION DeviceExtension,IN UCHAR Status,IN UCHAR Value)630 i8042MouResetIsr(
631 	IN PI8042_MOUSE_EXTENSION DeviceExtension,
632 	IN UCHAR Status,
633 	IN UCHAR Value)
634 {
635 	PPORT_DEVICE_EXTENSION PortDeviceExtension;
636 	BOOLEAN ToReturn = FALSE;
637 
638 	if (i8042MouCallIsrHook(DeviceExtension, Status, Value, &ToReturn))
639 		return ToReturn;
640 
641 	if (MouseIdle == DeviceExtension->MouseState)
642 	{
643 		/* Magic packet value that indicates a reset */
644 		if (0xAA == Value)
645 		{
646 			WARN_(I8042PRT, "Hot plugged mouse!\n");
647 			DeviceExtension->MouseState = MouseResetting;
648 			DeviceExtension->MouseResetState = ExpectingReset;
649 		}
650 		else
651 			return FALSE;
652 	}
653 	else if (MouseResetting != DeviceExtension->MouseState)
654 		return FALSE;
655 
656 	DeviceExtension->MouseTimeoutState = TimeoutStart;
657 	PortDeviceExtension = DeviceExtension->Common.PortDeviceExtension;
658 
659 	switch ((ULONG)DeviceExtension->MouseResetState)
660 	{
661 		case ExpectingReset:
662 			if (MOUSE_ACK == Value)
663 			{
664 				WARN_(I8042PRT, "Dropping extra ACK\n");
665 				return TRUE;
666 			}
667 
668 			/* First, 0xFF is sent. The mouse is supposed to say AA00 if ok, FC00 if not. */
669 			if (0xAA == Value)
670 			{
671 				DeviceExtension->MouseResetState++;
672 			}
673 			else
674 			{
675 				PortDeviceExtension->Flags &= ~MOUSE_PRESENT;
676 				DeviceExtension->MouseState = MouseIdle;
677 				WARN_(I8042PRT, "Mouse returned bad reset reply: %x (expected aa)\n", Value);
678 			}
679 			return TRUE;
680 		case ExpectingResetId:
681 			if (MOUSE_ACK == Value)
682 			{
683 				WARN_(I8042PRT, "Dropping extra ACK #2\n");
684 				return TRUE;
685 			}
686 
687 			if (0x00 == Value)
688 			{
689 				DeviceExtension->MouseResetState++;
690 				DeviceExtension->MouseType = GenericPS2;
691 				DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF2);
692 			}
693 			else
694 			{
695 				PortDeviceExtension->Flags &= ~MOUSE_PRESENT;
696 				DeviceExtension->MouseState = MouseIdle;
697 				WARN_(I8042PRT, "Mouse returned bad reset reply part two: %x (expected 0)\n", Value);
698 			}
699 			return TRUE;
700 		case ExpectingGetDeviceIdACK:
701 			if (MOUSE_ACK == Value)
702 			{
703 				DeviceExtension->MouseResetState++;
704 			}
705 			else if (MOUSE_NACK == Value || MOUSE_ERROR == Value)
706 			{
707 				DeviceExtension->MouseResetState++;
708 				/* Act as if 00 (normal mouse) was received */
709 				WARN_(I8042PRT, "Mouse doesn't support 0xd2, (returns %x, expected %x), faking\n", Value, MOUSE_ACK);
710 				i8042MouResetIsr(DeviceExtension, Status, 0);
711 			}
712 			return TRUE;
713 		case ExpectingGetDeviceIdValue:
714 			switch (Value)
715 			{
716 				case 0x02:
717 					DeviceExtension->MouseAttributes.MouseIdentifier =
718 						BALLPOINT_I8042_HARDWARE;
719 					break;
720 				case 0x03:
721 				case 0x04:
722 					DeviceExtension->MouseAttributes.MouseIdentifier =
723 						WHEELMOUSE_I8042_HARDWARE;
724 					break;
725 				default:
726 					DeviceExtension->MouseAttributes.MouseIdentifier =
727 						MOUSE_I8042_HARDWARE;
728 			}
729 			DeviceExtension->MouseResetState++;
730 			DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xE8);
731 			return TRUE;
732 		case ExpectingSetResolutionDefaultACK:
733 			DeviceExtension->MouseResetState++;
734 			DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0x00);
735 			return TRUE;
736 		case ExpectingSetResolutionDefaultValueACK:
737 			DeviceExtension->MouseResetState = ExpectingSetScaling1to1ACK;
738 			DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xE6);
739 			return TRUE;
740 		case ExpectingSetScaling1to1ACK:
741 		case ExpectingSetScaling1to1ACK2:
742 			DeviceExtension->MouseResetState++;
743 			DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xE6);
744 			return TRUE;
745 		case ExpectingSetScaling1to1ACK3:
746 			DeviceExtension->MouseResetState++;
747 			DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xE9);
748 			return TRUE;
749 		case ExpectingReadMouseStatusACK:
750 			DeviceExtension->MouseResetState++;
751 			return TRUE;
752 		case ExpectingReadMouseStatusByte1:
753 			DeviceExtension->MouseLogiBuffer[0] = Value;
754 			DeviceExtension->MouseResetState++;
755 			return TRUE;
756 		case ExpectingReadMouseStatusByte2:
757 			DeviceExtension->MouseLogiBuffer[1] = Value;
758 			DeviceExtension->MouseResetState++;
759 			return TRUE;
760 		case ExpectingReadMouseStatusByte3:
761 			DeviceExtension->MouseLogiBuffer[2] = Value;
762 			/* Now MouseLogiBuffer is a set of info. If the second
763 			 * byte is 0, the mouse didn't understand the magic
764 			 * code. Otherwise, it it a Logitech and the second byte
765 			 * is the number of buttons, bit 7 of the first byte tells
766 			 * if it understands special E7 commands, the rest is an ID.
767 			 */
768 			if (DeviceExtension->MouseLogiBuffer[1])
769 			{
770 				DeviceExtension->MouseAttributes.NumberOfButtons =
771 					DeviceExtension->MouseLogiBuffer[1];
772 				DeviceExtension->MouseType = Ps2pp;
773 				DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF3);
774 				DeviceExtension->MouseResetState = ExpectingSetSamplingRateACK;
775 				/* TODO: Go through EnableWheel and Enable5Buttons */
776 				return TRUE;
777 			}
778 			DeviceExtension->MouseResetState = EnableWheel;
779 			i8042MouResetIsr(DeviceExtension, Status, Value);
780 			return TRUE;
781 		case EnableWheel:
782 			DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF3);
783 			DeviceExtension->MouseResetState = 1001;
784 			return TRUE;
785 		case 1001:
786 			DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xC8);
787 			DeviceExtension->MouseResetState++;
788 			return TRUE;
789 		case 1002:
790 		case 1004:
791 			DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF3);
792 			DeviceExtension->MouseResetState++;
793 			return TRUE;
794 		case 1003:
795 			DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0x64);
796 			DeviceExtension->MouseResetState++;
797 			return TRUE;
798 		case 1005:
799 			DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0x50);
800 			DeviceExtension->MouseResetState++;
801 			return TRUE;
802 		case 1006:
803 			DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF2);
804 			DeviceExtension->MouseResetState++;
805 			return TRUE;
806 		case 1007:
807 			/* Ignore ACK */
808 			DeviceExtension->MouseResetState++;
809 			return TRUE;
810 		case 1008:
811 			if (0x03 == Value) {
812 				/* It's either an Intellimouse or Intellimouse Explorer. */
813 				DeviceExtension->MouseAttributes.NumberOfButtons = 3;
814 				DeviceExtension->MouseAttributes.MouseIdentifier =
815 					WHEELMOUSE_I8042_HARDWARE;
816 				DeviceExtension->MouseType = Intellimouse;
817 				DeviceExtension->MouseResetState = Enable5Buttons;
818 				i8042MouResetIsr(DeviceExtension, Status, Value);
819 			}
820 			else
821 			{
822 				/* Just set the default settings and be done */
823 				DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF3);
824 				DeviceExtension->MouseResetState = ExpectingSetSamplingRateACK;
825 			}
826 			return TRUE;
827 		case Enable5Buttons:
828 			DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF3);
829 			DeviceExtension->MouseResetState = 1021;
830 			return TRUE;
831 		case 1022:
832 		case 1024:
833 			DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF3);
834 			DeviceExtension->MouseResetState++;
835 			return TRUE;
836 		case 1021:
837 		case 1023:
838 			DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xC8);
839 			DeviceExtension->MouseResetState++;
840 			return TRUE;
841 		case 1025:
842 			DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0x50);
843 			DeviceExtension->MouseResetState++;
844 			return TRUE;
845 		case 1026:
846 			DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF2);
847 			DeviceExtension->MouseResetState++;
848 			return TRUE;
849 		case 1027:
850 			if (0x04 == Value)
851 			{
852 				DeviceExtension->MouseAttributes.NumberOfButtons = 5;
853 				DeviceExtension->MouseAttributes.MouseIdentifier =
854 					WHEELMOUSE_I8042_HARDWARE;
855 				DeviceExtension->MouseType = IntellimouseExplorer;
856 			}
857 			DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF3);
858 			DeviceExtension->MouseResetState = ExpectingSetSamplingRateACK;
859 			return TRUE;
860 		case ExpectingSetSamplingRateACK:
861 			DeviceExtension->MouseHook.IsrWritePort(
862 				DeviceExtension->MouseHook.CallContext,
863 				(UCHAR)DeviceExtension->MouseAttributes.SampleRate);
864 			DeviceExtension->MouseResetState++;
865 			return TRUE;
866 		case ExpectingSetSamplingRateValueACK:
867 			if (MOUSE_NACK == Value)
868 			{
869 				DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0x3C);
870 				DeviceExtension->MouseAttributes.SampleRate = (USHORT)PortDeviceExtension->Settings.SampleRate;
871 				DeviceExtension->MouseResetState = 1040;
872 				return TRUE;
873 			}
874 		case 1040:  /* Fallthrough */
875 			DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xE8);
876 			DeviceExtension->MouseResetState = ExpectingFinalResolutionACK;
877 			return TRUE;
878 		case ExpectingFinalResolutionACK:
879 			DeviceExtension->MouseHook.IsrWritePort(
880 				DeviceExtension->MouseHook.CallContext,
881 				(UCHAR)(PortDeviceExtension->Settings.MouseResolution & 0xff));
882 			INFO_(I8042PRT, "Mouse resolution %lu\n",
883 				PortDeviceExtension->Settings.MouseResolution);
884 			DeviceExtension->MouseResetState = ExpectingFinalResolutionValueACK;
885 			return TRUE;
886 		case ExpectingFinalResolutionValueACK:
887 			DeviceExtension->MouseHook.IsrWritePort(DeviceExtension->MouseHook.CallContext, 0xF4);
888 			DeviceExtension->MouseResetState = ExpectingEnableACK;
889 			return TRUE;
890 		case ExpectingEnableACK:
891 			PortDeviceExtension->Flags |= MOUSE_PRESENT;
892 			DeviceExtension->MouseState = MouseIdle;
893 			DeviceExtension->MouseTimeoutState = TimeoutCancel;
894 			INFO_(I8042PRT, "Mouse type = %u\n", DeviceExtension->MouseType);
895 			return TRUE;
896 		default:
897 			if (DeviceExtension->MouseResetState < 100 || DeviceExtension->MouseResetState > 999)
898 				ERR_(I8042PRT, "MouseResetState went out of range: %lu\n", DeviceExtension->MouseResetState);
899 			return FALSE;
900 	}
901 }
902 
903 BOOLEAN NTAPI
i8042MouInterruptService(IN PKINTERRUPT Interrupt,PVOID Context)904 i8042MouInterruptService(
905 	IN PKINTERRUPT Interrupt,
906 	PVOID Context)
907 {
908 	PI8042_MOUSE_EXTENSION DeviceExtension;
909 	PPORT_DEVICE_EXTENSION PortDeviceExtension;
910 	ULONG Counter;
911 	UCHAR Output = 0, PortStatus = 0;
912 	NTSTATUS Status;
913 
914 	UNREFERENCED_PARAMETER(Interrupt);
915 
916 	__analysis_assume(Context != NULL);
917 	DeviceExtension = Context;
918 	PortDeviceExtension = DeviceExtension->Common.PortDeviceExtension;
919 	Counter = PortDeviceExtension->Settings.PollStatusIterations;
920 
921 	while (Counter)
922 	{
923 		Status = i8042ReadStatus(PortDeviceExtension, &PortStatus);
924 		if (!NT_SUCCESS(Status))
925 		{
926 			WARN_(I8042PRT, "i8042ReadStatus() failed with status 0x%08lx\n", Status);
927 			return FALSE;
928 		}
929 		Status = i8042ReadMouseData(PortDeviceExtension, &Output);
930 		if (NT_SUCCESS(Status))
931 			break;
932 		KeStallExecutionProcessor(1);
933 		Counter--;
934 	}
935 	if (Counter == 0)
936 	{
937 		WARN_(I8042PRT, "Spurious i8042 mouse interrupt\n");
938 		return FALSE;
939 	}
940 
941 	INFO_(I8042PRT, "Got: 0x%02x\n", Output);
942 
943 	if (i8042PacketIsr(PortDeviceExtension, Output))
944 	{
945 		if (PortDeviceExtension->PacketComplete)
946 		{
947 			TRACE_(I8042PRT, "Packet complete\n");
948 			KeInsertQueueDpc(&DeviceExtension->DpcMouse, NULL, NULL);
949 		}
950 		TRACE_(I8042PRT, "Irq eaten by packet\n");
951 		return TRUE;
952 	}
953 
954 	TRACE_(I8042PRT, "Irq is mouse input\n");
955 
956 	i8042MouInputTestTimeout(DeviceExtension);
957 
958 	if (i8042MouResetIsr(DeviceExtension, PortStatus, Output))
959 	{
960 		TRACE_(I8042PRT, "Handled by ResetIsr or hooked Isr\n");
961 		if (NoChange != DeviceExtension->MouseTimeoutState) {
962 			KeInsertQueueDpc(&DeviceExtension->DpcMouse, NULL, NULL);
963 		}
964 		return TRUE;
965 	}
966 
967 	if (DeviceExtension->MouseType == Ps2pp)
968 		i8042MouHandlePs2pp(DeviceExtension, Output);
969 	else
970 		i8042MouHandle(DeviceExtension, Output);
971 
972 	return TRUE;
973 }
974