xref: /reactos/subsystems/mvdm/ntvdm/hardware/ps2.c (revision c2c66aff)
1 /*
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)
8  *
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/
12  */
13 
14 /* INCLUDES *******************************************************************/
15 
16 #include "ntvdm.h"
17 
18 #define NDEBUG
19 #include <debug.h>
20 
21 #include "emulator.h"
22 #include "ps2.h"
23 
24 #include "memory.h"
25 #include "io.h"
26 #include "pic.h"
27 #include "clock.h"
28 
29 /* PRIVATE VARIABLES **********************************************************/
30 
31 #define BUFFER_SIZE 32
32 
33 typedef struct _PS2_PORT
34 {
35     BOOLEAN IsEnabled;
36 
37     BOOLEAN QueueEmpty;
38     BYTE    Queue[BUFFER_SIZE];
39     UINT    QueueStart;
40     UINT    QueueEnd;
41     HANDLE  QueueMutex;
42 
43     LPVOID             Param;
44     PS2_DEVICE_CMDPROC DeviceCommand;
45 } PS2_PORT, *PPS2_PORT;
46 
47 /*
48  * Port 1: Keyboard
49  * Port 2: Mouse
50  */
51 #define PS2_PORTS  2
52 static PS2_PORT Ports[PS2_PORTS];
53 
54 static BYTE Memory[0x20]; // PS/2 Controller internal memory
55 #define ControllerConfig Memory[0] // The configuration byte is byte 0
56 
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
60 
61 static BYTE ControllerCommand = 0x00;
62 
63 static PHARDWARE_TIMER IrqTimer = NULL;
64 
65 /* PRIVATE FUNCTIONS **********************************************************/
66 
PS2SendCommand(BYTE PS2Port,BYTE Command)67 static VOID PS2SendCommand(BYTE PS2Port, BYTE Command)
68 {
69     PPS2_PORT Port;
70 
71     ASSERT(PS2Port < PS2_PORTS);
72     Port = &Ports[PS2Port];
73 
74     /*
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.
77      */
78     if (PS2Port == 0)
79         ControllerConfig &= ~PS2_CONFIG_KBD_DISABLE;
80     else // if (PS2Port == 1)
81         ControllerConfig &= ~PS2_CONFIG_AUX_DISABLE;
82 
83     Port->IsEnabled = TRUE;
84 
85     /* Call the device command */
86     if (Port->DeviceCommand) Port->DeviceCommand(Port->Param, Command);
87 }
88 
89 
PS2ReadControl(USHORT Port)90 static BYTE WINAPI PS2ReadControl(USHORT Port)
91 {
92     UNREFERENCED_PARAMETER(Port);
93 
94     /*
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/
100      */
101     StatusRegister |= PS2_STAT_KBD_ENABLE;
102 
103     /* We do not have any timeouts nor parity errors */
104     StatusRegister &= ~(PS2_STAT_PARITY_ERROR | PS2_STAT_GEN_TIMEOUT);
105 
106     return StatusRegister;
107 }
108 
PS2ReadData(USHORT Port)109 static BYTE WINAPI PS2ReadData(USHORT Port)
110 {
111     UNREFERENCED_PARAMETER(Port);
112 
113     /*
114      * If there is something to read (response byte from the
115      * controller or data from a PS/2 device), read it.
116      */
117     StatusRegister &= ~PS2_STAT_OUT_BUF_FULL;
118 
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;
122 
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...
125 
126     /* Always return the available byte stored in the output buffer */
127     return OutputBuffer;
128 }
129 
PS2WriteControl(USHORT Port,BYTE Data)130 static VOID WINAPI PS2WriteControl(USHORT Port, BYTE Data)
131 {
132     UNREFERENCED_PARAMETER(Port);
133 
134     switch (Data)
135     {
136         /* Read configuration byte (byte 0 from internal memory) */
137         case 0x20:
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:
147         {
148             OutputBuffer = Memory[Data & 0x1F];
149             StatusRegister |= PS2_STAT_OUT_BUF_FULL;
150             break;
151         }
152 
153         /* Write configuration byte (byte 0 from internal memory) */
154         case 0x60:
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:
164 
165         /* Write controller output port */
166         case 0xD1:
167         /* Write to the first PS/2 port output buffer */
168         case 0xD2:
169         /* Write to the second PS/2 port output buffer */
170         case 0xD3:
171         /* Write to the second PS/2 port input buffer */
172         case 0xD4:
173         {
174             /* These commands require a response */
175             ControllerCommand = Data;
176             StatusRegister |= PS2_STAT_COMMAND;
177             break;
178         }
179 
180         /* Disable second PS/2 port */
181         case 0xA7:
182         {
183             ControllerConfig |= PS2_CONFIG_AUX_DISABLE;
184             Ports[1].IsEnabled = FALSE;
185             break;
186         }
187 
188         /* Enable second PS/2 port */
189         case 0xA8:
190         {
191             ControllerConfig &= ~PS2_CONFIG_AUX_DISABLE;
192             Ports[1].IsEnabled = TRUE;
193             break;
194         }
195 
196         /* Test second PS/2 port */
197         case 0xA9:
198         {
199             OutputBuffer = 0x00; // Success code
200             StatusRegister |= PS2_STAT_OUT_BUF_FULL;
201             break;
202         }
203 
204         /* Test PS/2 controller */
205         case 0xAA:
206         {
207             OutputBuffer = 0x55; // Success code
208             StatusRegister |= PS2_STAT_OUT_BUF_FULL;
209             break;
210         }
211 
212         /* Test first PS/2 port */
213         case 0xAB:
214         {
215             OutputBuffer = 0x00; // Success code
216             StatusRegister |= PS2_STAT_OUT_BUF_FULL;
217             break;
218         }
219 
220         /* Disable first PS/2 port */
221         case 0xAD:
222         {
223             ControllerConfig |= PS2_CONFIG_KBD_DISABLE;
224             Ports[0].IsEnabled = FALSE;
225             break;
226         }
227 
228         /* Enable first PS/2 port */
229         case 0xAE:
230         {
231             ControllerConfig &= ~PS2_CONFIG_KBD_DISABLE;
232             Ports[0].IsEnabled = TRUE;
233             break;
234         }
235 
236         /* Read controller output port */
237         case 0xD0:
238         {
239             /* Bit 0 always set, and bit 1 is the A20 gate state */
240             OutputBuffer = (!!EmulatorGetA20() << 1) | PS2_OUT_CPU_NO_RESET;
241 
242             /* Set IRQ1 state */
243             if (ControllerConfig & PS2_CONFIG_KBD_INT)
244                 OutputBuffer |=  PS2_OUT_IRQ01;
245             else
246                 OutputBuffer &= ~PS2_OUT_IRQ01;
247 
248             /* Set IRQ12 state */
249             if (ControllerConfig & PS2_CONFIG_AUX_INT)
250                 OutputBuffer |=  PS2_OUT_IRQ12;
251             else
252                 OutputBuffer &= ~PS2_OUT_IRQ12;
253 
254             /* Check whether data is already present */
255             if (StatusRegister & PS2_STAT_OUT_BUF_FULL)
256             {
257                 if (StatusRegister & PS2_STAT_AUX_OUT_BUF_FULL)
258                     OutputBuffer |= PS2_OUT_AUX_DATA;
259                 else
260                     OutputBuffer |= PS2_OUT_KBD_DATA;
261             }
262 
263             StatusRegister |= PS2_STAT_OUT_BUF_FULL;
264             break;
265         }
266 
267         /* CPU Reset */
268         case 0xF0: case 0xF2: case 0xF4: case 0xF6:
269         case 0xF8: case 0xFA: case 0xFC: case 0xFE:
270         {
271             /* Stop the VDM */
272             EmulatorTerminate();
273             return;
274         }
275     }
276 }
277 
PS2WriteData(USHORT Port,BYTE Data)278 static VOID WINAPI PS2WriteData(USHORT Port, BYTE Data)
279 {
280     /* Check if the controller is waiting for a response */
281     if (StatusRegister & PS2_STAT_COMMAND)
282     {
283         StatusRegister &= ~PS2_STAT_COMMAND;
284 
285         /* Check which command it was */
286         switch (ControllerCommand)
287         {
288             /* Write configuration byte (byte 0 from internal memory) */
289             case 0x60:
290             {
291                 ControllerConfig = Data;
292 
293                 /*
294                  * Synchronize the enable state of the PS/2 ports
295                  * with the flags in the configuration byte.
296                  */
297                 Ports[0].IsEnabled = !(ControllerConfig & PS2_CONFIG_KBD_DISABLE);
298                 Ports[1].IsEnabled = !(ControllerConfig & PS2_CONFIG_AUX_DISABLE);
299 
300                 /*
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
304                  * for more details.
305                  */
306                 if (ControllerConfig & PS2_CONFIG_SYSTEM)
307                     StatusRegister |=  PS2_STAT_SYSTEM;
308                 else
309                     StatusRegister &= ~PS2_STAT_SYSTEM;
310 
311                 /*
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
316                  * for more details.
317                  */
318                 if (ControllerConfig & PS2_CONFIG_NO_KEYLOCK)
319                 {
320                     ControllerConfig &= ~PS2_CONFIG_NO_KEYLOCK;
321                     StatusRegister   |=  PS2_STAT_KBD_ENABLE;
322                 }
323 
324                 break;
325             }
326 
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:
336             {
337                 Memory[ControllerCommand & 0x1F] = Data;
338                 break;
339             }
340 
341             /* Write controller output */
342             case 0xD1:
343             {
344                 /* Check if bit 0 is unset */
345                 if (!(Data & PS2_OUT_CPU_NO_RESET))
346                 {
347                     /* CPU disabled - Stop the VDM */
348                     EmulatorTerminate();
349                     return;
350                 }
351 
352                 /* Update the A20 line setting */
353                 EmulatorSetA20(Data & PS2_OUT_A20_SET);
354 
355                 // FIXME: Should we need to add the status of IRQ1 and IRQ12??
356 
357                 break;
358             }
359 
360             /* Push the data byte into the first PS/2 port queue */
361             case 0xD2:
362             {
363                 PS2QueuePush(0, Data);
364                 break;
365             }
366 
367             /* Push the data byte into the second PS/2 port queue */
368             case 0xD3:
369             {
370                 PS2QueuePush(1, Data);
371                 break;
372             }
373 
374             /*
375              * Send a command to the second PS/2 port (by default
376              * it is a command for the first PS/2 port)
377              */
378             case 0xD4:
379             {
380                 PS2SendCommand(1, Data);
381                 break;
382             }
383         }
384 
385         return;
386     }
387 
388     /* By default, send a command to the first PS/2 port */
389     PS2SendCommand(0, Data);
390 }
391 
GeneratePS2Irq(ULONGLONG ElapsedTime)392 static VOID FASTCALL GeneratePS2Irq(ULONGLONG ElapsedTime)
393 {
394     UNREFERENCED_PARAMETER(ElapsedTime);
395 
396     /*
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.
400      *
401      * NOTE: The first PS/2 port (keyboard) has priority over the second one (mouse).
402      */
403     if (PS2PortQueueRead(0))
404     {
405         if (ControllerConfig & PS2_CONFIG_KBD_INT) PicInterruptRequest(1);
406     }
407     else if (PS2PortQueueRead(1))
408     {
409         if (ControllerConfig & PS2_CONFIG_AUX_INT) PicInterruptRequest(12);
410     }
411 }
412 
413 /* PUBLIC FUNCTIONS ***********************************************************/
414 
PS2PortQueueRead(BYTE PS2Port)415 BOOLEAN PS2PortQueueRead(BYTE PS2Port)
416 {
417     BOOLEAN Result = FALSE;
418     PPS2_PORT Port;
419 
420     // NOTE: The first PS/2 port (keyboard) has priority over the second one (mouse).
421 
422     Port = &Ports[PS2Port];
423 
424     if (!Port->IsEnabled) return FALSE;
425 
426     /* Make sure the queue is not empty (fast check) */
427     if (Port->QueueEmpty)
428     {
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??
432         if (PS2Port == 0)
433         {
434             OutputBuffer = Port->Queue[(Port->QueueStart - 1) % BUFFER_SIZE];
435             StatusRegister &= ~PS2_STAT_AUX_OUT_BUF_FULL; // Clear flag: keyboard data
436         }
437 
438         return FALSE;
439     }
440 
441     WaitForSingleObject(Port->QueueMutex, INFINITE);
442 
443     /*
444      * Recheck whether the queue is not empty (it may
445      * have changed after having grabbed the mutex).
446      */
447     if (Port->QueueEmpty) goto Done;
448 
449     /* Get the data */
450     OutputBuffer = Port->Queue[Port->QueueStart];
451 
452     // StatusRegister &= ~(PS2_STAT_AUX_OUT_BUF_FULL | PS2_STAT_OUT_BUF_FULL);
453 
454     /* Always set the "Output buffer full" flag */
455     StatusRegister |= PS2_STAT_OUT_BUF_FULL;
456 
457     /* Set the "Auxiliary output buffer full" flag according to where the data came from */
458     if (PS2Port == 0)
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
462 
463     /* Remove the value from the queue */
464     Port->QueueStart++;
465     Port->QueueStart %= BUFFER_SIZE;
466 
467     /* Check if the queue is now empty */
468     if (Port->QueueStart == Port->QueueEnd)
469         Port->QueueEmpty = TRUE;
470 
471     Result = TRUE;
472 
473 Done:
474     ReleaseMutex(Port->QueueMutex);
475     return Result;
476 }
477 
PS2SetDeviceCmdProc(BYTE PS2Port,LPVOID Param,PS2_DEVICE_CMDPROC DeviceCommand)478 VOID PS2SetDeviceCmdProc(BYTE PS2Port, LPVOID Param, PS2_DEVICE_CMDPROC DeviceCommand)
479 {
480     ASSERT(PS2Port < PS2_PORTS);
481     Ports[PS2Port].Param         = Param;
482     Ports[PS2Port].DeviceCommand = DeviceCommand;
483 }
484 
485 // PS2SendToPort
PS2QueuePush(BYTE PS2Port,BYTE Data)486 BOOLEAN PS2QueuePush(BYTE PS2Port, BYTE Data)
487 {
488     BOOLEAN Result = FALSE;
489     PPS2_PORT Port;
490 
491     ASSERT(PS2Port < PS2_PORTS);
492     Port = &Ports[PS2Port];
493 
494     if (!Port->IsEnabled) return FALSE;
495 
496     WaitForSingleObject(Port->QueueMutex, INFINITE);
497 
498     /* Check if the queue is full */
499     if (!Port->QueueEmpty && (Port->QueueStart == Port->QueueEnd))
500         goto Done;
501 
502     /* Insert the value in the queue */
503     Port->Queue[Port->QueueEnd] = Data;
504     Port->QueueEnd++;
505     Port->QueueEnd %= BUFFER_SIZE;
506 
507     /* The queue is not empty anymore */
508     Port->QueueEmpty = FALSE;
509 
510     /* Schedule the IRQ */
511     EnableHardwareTimer(IrqTimer);
512 
513     Result = TRUE;
514 
515 Done:
516     ReleaseMutex(Port->QueueMutex);
517     return Result;
518 }
519 
PS2Initialize(VOID)520 BOOLEAN PS2Initialize(VOID)
521 {
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);
528 
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);
534 
535     /* Register the I/O Ports */
536     RegisterIoPort(PS2_CONTROL_PORT, PS2ReadControl, PS2WriteControl);
537     RegisterIoPort(PS2_DATA_PORT   , PS2ReadData   , PS2WriteData   );
538 
539     IrqTimer = CreateHardwareTimer(HARDWARE_TIMER_ONESHOT,
540                                    HZ_TO_NS(100),
541                                    GeneratePS2Irq);
542 
543     return TRUE;
544 }
545 
PS2Cleanup(VOID)546 VOID PS2Cleanup(VOID)
547 {
548     DestroyHardwareTimer(IrqTimer);
549 
550     CloseHandle(Ports[1].QueueMutex);
551     CloseHandle(Ports[0].QueueMutex);
552 }
553 
554 /* EOF */
555