2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: subsystems/mvdm/ntvdm/hardware/ps2.c
5 * PURPOSE: PS/2 controller emulation
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 * Hermes Belusca-Maito (hermes.belusca@sfr.fr)
9 * DOCUMENTATION: IBM Personal System/2 Hardware Interface Technical Reference, May 1988 (Section 10)
10 * http://wiki.osdev.org/%228042%22_PS/2_Controller
11 * http://www.computer-engineering.org/ps2keyboard/
14 /* INCLUDES *******************************************************************/
29 /* PRIVATE VARIABLES **********************************************************/
31 #define BUFFER_SIZE 32
33 typedef struct _PS2_PORT
38 BYTE Queue
[BUFFER_SIZE
];
44 PS2_DEVICE_CMDPROC DeviceCommand
;
45 } PS2_PORT
, *PPS2_PORT
;
52 static PS2_PORT Ports
[PS2_PORTS
];
54 static BYTE Memory
[0x20]; // PS/2 Controller internal memory
55 #define ControllerConfig Memory[0] // The configuration byte is byte 0
57 static BYTE StatusRegister
= 0x00; // Controller status register
58 // static BYTE InputBuffer = 0x00; // PS/2 Input Buffer
59 static BYTE OutputBuffer
= 0x00; // PS/2 Output Buffer
61 static BYTE ControllerCommand
= 0x00;
63 static PHARDWARE_TIMER IrqTimer
= NULL
;
65 /* PRIVATE FUNCTIONS **********************************************************/
67 static VOID
PS2SendCommand(BYTE PS2Port
, BYTE Command
)
71 ASSERT(PS2Port
< PS2_PORTS
);
72 Port
= &Ports
[PS2Port
];
75 * According to http://www.win.tue.nl/~aeb/linux/kbd/scancodes-11.html#kccad
76 * any PS/2 command sent reenables the corresponding port.
79 ControllerConfig
&= ~PS2_CONFIG_KBD_DISABLE
;
80 else // if (PS2Port == 1)
81 ControllerConfig
&= ~PS2_CONFIG_AUX_DISABLE
;
83 Port
->IsEnabled
= TRUE
;
85 /* Call the device command */
86 if (Port
->DeviceCommand
) Port
->DeviceCommand(Port
->Param
, Command
);
90 static BYTE WINAPI
PS2ReadControl(USHORT Port
)
92 UNREFERENCED_PARAMETER(Port
);
95 * Be sure the "Keyboard enable" flag is always set.
96 * On IBM PC-ATs this is the state of the hardware keyboard
97 * lock mechanism. It is not widely used, but some programs
98 * still use it, see for example:
99 * http://www.os2museum.com/wp/the-dos-4-0-shell-mouse-mystery/
101 StatusRegister
|= PS2_STAT_KBD_ENABLE
;
103 /* We do not have any timeouts nor parity errors */
104 StatusRegister
&= ~(PS2_STAT_PARITY_ERROR
| PS2_STAT_GEN_TIMEOUT
);
106 return StatusRegister
;
109 static BYTE WINAPI
PS2ReadData(USHORT Port
)
111 UNREFERENCED_PARAMETER(Port
);
114 * If there is something to read (response byte from the
115 * controller or data from a PS/2 device), read it.
117 StatusRegister
&= ~PS2_STAT_OUT_BUF_FULL
;
119 // Keep the state of the "Auxiliary output buffer full" flag
120 // in order to remember from where the data came from.
121 // StatusRegister &= ~PS2_STAT_AUX_OUT_BUF_FULL;
123 // FIXME: We may check there whether there is data latched in
124 // PS2 ports 1 or 2 (keyboard or mouse) and retrieve it there...
126 /* Always return the available byte stored in the output buffer */
130 static VOID WINAPI
PS2WriteControl(USHORT Port
, BYTE Data
)
132 UNREFERENCED_PARAMETER(Port
);
136 /* Read configuration byte (byte 0 from internal memory) */
138 /* Read byte N from internal memory */
139 case 0x21: case 0x22: case 0x23:
140 case 0x24: case 0x25: case 0x26: case 0x27:
141 case 0x28: case 0x29: case 0x2A: case 0x2B:
142 case 0x2C: case 0x2D: case 0x2E: case 0x2F:
143 case 0x30: case 0x31: case 0x32: case 0x33:
144 case 0x34: case 0x35: case 0x36: case 0x37:
145 case 0x38: case 0x39: case 0x3A: case 0x3B:
146 case 0x3C: case 0x3D: case 0x3E: case 0x3F:
148 OutputBuffer
= Memory
[Data
& 0x1F];
149 StatusRegister
|= PS2_STAT_OUT_BUF_FULL
;
153 /* Write configuration byte (byte 0 from internal memory) */
155 /* Write to byte N of internal memory */
156 case 0x61: case 0x62: case 0x63:
157 case 0x64: case 0x65: case 0x66: case 0x67:
158 case 0x68: case 0x69: case 0x6A: case 0x6B:
159 case 0x6C: case 0x6D: case 0x6E: case 0x6F:
160 case 0x70: case 0x71: case 0x72: case 0x73:
161 case 0x74: case 0x75: case 0x76: case 0x77:
162 case 0x78: case 0x79: case 0x7A: case 0x7B:
163 case 0x7C: case 0x7D: case 0x7E: case 0x7F:
165 /* Write controller output port */
167 /* Write to the first PS/2 port output buffer */
169 /* Write to the second PS/2 port output buffer */
171 /* Write to the second PS/2 port input buffer */
174 /* These commands require a response */
175 ControllerCommand
= Data
;
176 StatusRegister
|= PS2_STAT_COMMAND
;
180 /* Disable second PS/2 port */
183 ControllerConfig
|= PS2_CONFIG_AUX_DISABLE
;
184 Ports
[1].IsEnabled
= FALSE
;
188 /* Enable second PS/2 port */
191 ControllerConfig
&= ~PS2_CONFIG_AUX_DISABLE
;
192 Ports
[1].IsEnabled
= TRUE
;
196 /* Test second PS/2 port */
199 OutputBuffer
= 0x00; // Success code
200 StatusRegister
|= PS2_STAT_OUT_BUF_FULL
;
204 /* Test PS/2 controller */
207 OutputBuffer
= 0x55; // Success code
208 StatusRegister
|= PS2_STAT_OUT_BUF_FULL
;
212 /* Test first PS/2 port */
215 OutputBuffer
= 0x00; // Success code
216 StatusRegister
|= PS2_STAT_OUT_BUF_FULL
;
220 /* Disable first PS/2 port */
223 ControllerConfig
|= PS2_CONFIG_KBD_DISABLE
;
224 Ports
[0].IsEnabled
= FALSE
;
228 /* Enable first PS/2 port */
231 ControllerConfig
&= ~PS2_CONFIG_KBD_DISABLE
;
232 Ports
[0].IsEnabled
= TRUE
;
236 /* Read controller output port */
239 /* Bit 0 always set, and bit 1 is the A20 gate state */
240 OutputBuffer
= (!!EmulatorGetA20() << 1) | PS2_OUT_CPU_NO_RESET
;
243 if (ControllerConfig
& PS2_CONFIG_KBD_INT
)
244 OutputBuffer
|= PS2_OUT_IRQ01
;
246 OutputBuffer
&= ~PS2_OUT_IRQ01
;
248 /* Set IRQ12 state */
249 if (ControllerConfig
& PS2_CONFIG_AUX_INT
)
250 OutputBuffer
|= PS2_OUT_IRQ12
;
252 OutputBuffer
&= ~PS2_OUT_IRQ12
;
254 /* Check whether data is already present */
255 if (StatusRegister
& PS2_STAT_OUT_BUF_FULL
)
257 if (StatusRegister
& PS2_STAT_AUX_OUT_BUF_FULL
)
258 OutputBuffer
|= PS2_OUT_AUX_DATA
;
260 OutputBuffer
|= PS2_OUT_KBD_DATA
;
263 StatusRegister
|= PS2_STAT_OUT_BUF_FULL
;
268 case 0xF0: case 0xF2: case 0xF4: case 0xF6:
269 case 0xF8: case 0xFA: case 0xFC: case 0xFE:
278 static VOID WINAPI
PS2WriteData(USHORT Port
, BYTE Data
)
280 /* Check if the controller is waiting for a response */
281 if (StatusRegister
& PS2_STAT_COMMAND
)
283 StatusRegister
&= ~PS2_STAT_COMMAND
;
285 /* Check which command it was */
286 switch (ControllerCommand
)
288 /* Write configuration byte (byte 0 from internal memory) */
291 ControllerConfig
= Data
;
294 * Synchronize the enable state of the PS/2 ports
295 * with the flags in the configuration byte.
297 Ports
[0].IsEnabled
= !(ControllerConfig
& PS2_CONFIG_KBD_DISABLE
);
298 Ports
[1].IsEnabled
= !(ControllerConfig
& PS2_CONFIG_AUX_DISABLE
);
301 * Update the "System enabled" flag of the status register
302 * with bit 2 of the controller configuration byte.
303 * See: http://www.win.tue.nl/~aeb/linux/kbd/scancodes-11.html#kccb2
306 if (ControllerConfig
& PS2_CONFIG_SYSTEM
)
307 StatusRegister
|= PS2_STAT_SYSTEM
;
309 StatusRegister
&= ~PS2_STAT_SYSTEM
;
312 * Update the "Keyboard enable" flag of the status register
313 * with the "Ignore keyboard lock" flag of the controller
314 * configuration byte (if set), then reset the latter one.
315 * See: http://www.win.tue.nl/~aeb/linux/kbd/scancodes-11.html#kccb3
318 if (ControllerConfig
& PS2_CONFIG_NO_KEYLOCK
)
320 ControllerConfig
&= ~PS2_CONFIG_NO_KEYLOCK
;
321 StatusRegister
|= PS2_STAT_KBD_ENABLE
;
327 /* Write to byte N of internal memory */
328 case 0x61: case 0x62: case 0x63:
329 case 0x64: case 0x65: case 0x66: case 0x67:
330 case 0x68: case 0x69: case 0x6A: case 0x6B:
331 case 0x6C: case 0x6D: case 0x6E: case 0x6F:
332 case 0x70: case 0x71: case 0x72: case 0x73:
333 case 0x74: case 0x75: case 0x76: case 0x77:
334 case 0x78: case 0x79: case 0x7A: case 0x7B:
335 case 0x7C: case 0x7D: case 0x7E: case 0x7F:
337 Memory
[ControllerCommand
& 0x1F] = Data
;
341 /* Write controller output */
344 /* Check if bit 0 is unset */
345 if (!(Data
& PS2_OUT_CPU_NO_RESET
))
347 /* CPU disabled - Stop the VDM */
352 /* Update the A20 line setting */
353 EmulatorSetA20(Data
& PS2_OUT_A20_SET
);
355 // FIXME: Should we need to add the status of IRQ1 and IRQ12??
360 /* Push the data byte into the first PS/2 port queue */
363 PS2QueuePush(0, Data
);
367 /* Push the data byte into the second PS/2 port queue */
370 PS2QueuePush(1, Data
);
375 * Send a command to the second PS/2 port (by default
376 * it is a command for the first PS/2 port)
380 PS2SendCommand(1, Data
);
388 /* By default, send a command to the first PS/2 port */
389 PS2SendCommand(0, Data
);
392 static VOID FASTCALL
GeneratePS2Irq(ULONGLONG ElapsedTime
)
394 UNREFERENCED_PARAMETER(ElapsedTime
);
397 * Pop out fresh new data from the PS/2 port output queues, and only
398 * in case there is data ready, generate an IRQ1 or IRQ12 depending
399 * on whether interrupts are enabled for the given port.
401 * NOTE: The first PS/2 port (keyboard) has priority over the second one (mouse).
403 if (PS2PortQueueRead(0))
405 if (ControllerConfig
& PS2_CONFIG_KBD_INT
) PicInterruptRequest(1);
407 else if (PS2PortQueueRead(1))
409 if (ControllerConfig
& PS2_CONFIG_AUX_INT
) PicInterruptRequest(12);
413 /* PUBLIC FUNCTIONS ***********************************************************/
415 BOOLEAN
PS2PortQueueRead(BYTE PS2Port
)
417 BOOLEAN Result
= FALSE
;
420 // NOTE: The first PS/2 port (keyboard) has priority over the second one (mouse).
422 Port
= &Ports
[PS2Port
];
424 if (!Port
->IsEnabled
) return FALSE
;
426 /* Make sure the queue is not empty (fast check) */
427 if (Port
->QueueEmpty
)
429 /* Only the keyboard should have its last data latched */
430 // FIXME: Alternatively this can be done in PS2ReadData when
431 // we read PS2_DATA_PORT. What is the best solution??
434 OutputBuffer
= Port
->Queue
[(Port
->QueueStart
- 1) % BUFFER_SIZE
];
435 StatusRegister
&= ~PS2_STAT_AUX_OUT_BUF_FULL
; // Clear flag: keyboard data
441 WaitForSingleObject(Port
->QueueMutex
, INFINITE
);
444 * Recheck whether the queue is not empty (it may
445 * have changed after having grabbed the mutex).
447 if (Port
->QueueEmpty
) goto Done
;
450 OutputBuffer
= Port
->Queue
[Port
->QueueStart
];
452 // StatusRegister &= ~(PS2_STAT_AUX_OUT_BUF_FULL | PS2_STAT_OUT_BUF_FULL);
454 /* Always set the "Output buffer full" flag */
455 StatusRegister
|= PS2_STAT_OUT_BUF_FULL
;
457 /* Set the "Auxiliary output buffer full" flag according to where the data came from */
459 StatusRegister
&= ~PS2_STAT_AUX_OUT_BUF_FULL
; // Clear flag: keyboard data
460 else // if (PS2Port == 1)
461 StatusRegister
|= PS2_STAT_AUX_OUT_BUF_FULL
; // Set flag: mouse data
463 /* Remove the value from the queue */
465 Port
->QueueStart
%= BUFFER_SIZE
;
467 /* Check if the queue is now empty */
468 if (Port
->QueueStart
== Port
->QueueEnd
)
469 Port
->QueueEmpty
= TRUE
;
474 ReleaseMutex(Port
->QueueMutex
);
478 VOID
PS2SetDeviceCmdProc(BYTE PS2Port
, LPVOID Param
, PS2_DEVICE_CMDPROC DeviceCommand
)
480 ASSERT(PS2Port
< PS2_PORTS
);
481 Ports
[PS2Port
].Param
= Param
;
482 Ports
[PS2Port
].DeviceCommand
= DeviceCommand
;
486 BOOLEAN
PS2QueuePush(BYTE PS2Port
, BYTE Data
)
488 BOOLEAN Result
= FALSE
;
491 ASSERT(PS2Port
< PS2_PORTS
);
492 Port
= &Ports
[PS2Port
];
494 if (!Port
->IsEnabled
) return FALSE
;
496 WaitForSingleObject(Port
->QueueMutex
, INFINITE
);
498 /* Check if the queue is full */
499 if (!Port
->QueueEmpty
&& (Port
->QueueStart
== Port
->QueueEnd
))
502 /* Insert the value in the queue */
503 Port
->Queue
[Port
->QueueEnd
] = Data
;
505 Port
->QueueEnd
%= BUFFER_SIZE
;
507 /* The queue is not empty anymore */
508 Port
->QueueEmpty
= FALSE
;
510 /* Schedule the IRQ */
511 EnableHardwareTimer(IrqTimer
);
516 ReleaseMutex(Port
->QueueMutex
);
520 BOOLEAN
PS2Initialize(VOID
)
522 /* Initialize the PS/2 ports */
523 Ports
[0].IsEnabled
= FALSE
;
524 Ports
[0].QueueEmpty
= TRUE
;
525 Ports
[0].QueueStart
= 0;
526 Ports
[0].QueueEnd
= 0;
527 Ports
[0].QueueMutex
= CreateMutex(NULL
, FALSE
, NULL
);
529 Ports
[1].IsEnabled
= FALSE
;
530 Ports
[1].QueueEmpty
= TRUE
;
531 Ports
[1].QueueStart
= 0;
532 Ports
[1].QueueEnd
= 0;
533 Ports
[1].QueueMutex
= CreateMutex(NULL
, FALSE
, NULL
);
535 /* Register the I/O Ports */
536 RegisterIoPort(PS2_CONTROL_PORT
, PS2ReadControl
, PS2WriteControl
);
537 RegisterIoPort(PS2_DATA_PORT
, PS2ReadData
, PS2WriteData
);
539 IrqTimer
= CreateHardwareTimer(HARDWARE_TIMER_ONESHOT
,
546 VOID
PS2Cleanup(VOID
)
548 DestroyHardwareTimer(IrqTimer
);
550 CloseHandle(Ports
[1].QueueMutex
);
551 CloseHandle(Ports
[0].QueueMutex
);