1 /*
2  * COPYRIGHT:       GPL - See COPYING in the top level directory
3  * PROJECT:         ReactOS Virtual DOS Machine
4  * FILE:            subsystems/mvdm/ntvdm/hardware/keyboard.c
5  * PURPOSE:         Keyboard emulation
6  * PROGRAMMERS:     Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7  */
8 
9 /* INCLUDES *******************************************************************/
10 
11 #include "ntvdm.h"
12 
13 #define NDEBUG
14 #include <debug.h>
15 
16 #include "keyboard.h"
17 #include "ps2.h"
18 
19 /* PRIVATE VARIABLES **********************************************************/
20 
21 static BOOLEAN KeyboardReporting = FALSE;
22 static BYTE KeyboardId = 0; // We only support basic old-type keyboard
23 static BYTE KbdDataByteWait = 0;
24 
25 static BYTE KbdPS2Port = 0;
26 
27 /* PRIVATE FUNCTIONS **********************************************************/
28 
29 static VOID WINAPI KeyboardCommand(LPVOID Param, BYTE Command)
30 {
31     /* Check if we were waiting for a data byte */
32     if (KbdDataByteWait)
33     {
34         PS2QueuePush(KbdPS2Port, KEYBOARD_ACK);
35 
36         switch (KbdDataByteWait)
37         {
38             /* Set/Reset Mode Indicators */
39             case 0xED:
40             {
41                 // Ignore setting the keyboard LEDs
42                 break;
43             }
44 
45             /* PS/2 Select/Read Alternate Scan Code Sets */
46             case 0xF0:
47             /* Set Typematic Rate/Delay */
48             case 0xF3:
49             {
50                 // FIXME: UNIMPLEMENTED; just return ACKnowledge.
51                 // This unblocks some programs that want to initialize
52                 // the keyboard by sending keyboard commands and then
53                 // performing polling on the port until "valid" data
54                 // comes out.
55                 DPRINT1("KeyboardCommand(0x%02X) NOT IMPLEMENTED\n", KbdDataByteWait);
56                 break;
57             }
58 
59             default:
60             {
61                 /* Shouldn't happen */
62                 ASSERT(FALSE);
63             }
64         }
65 
66         KbdDataByteWait = 0;
67         return;
68     }
69 
70     switch (Command)
71     {
72         /* Set/Reset Mode Indicators */
73         case 0xED:
74         /* PS/2 Select/Read Alternate Scan Code Sets */
75         case 0xF0:
76         /* Set Typematic Rate/Delay */
77         case 0xF3:
78         {
79             KbdDataByteWait = Command;
80             PS2QueuePush(KbdPS2Port, KEYBOARD_ACK);
81             break;
82         }
83 
84         /* Echo test command */
85         case 0xEE:
86         {
87             PS2QueuePush(KbdPS2Port, 0xEE);
88             break;
89         }
90 
91         /* Get Keyboard ID */
92         case 0xF2:
93         {
94             PS2QueuePush(KbdPS2Port, KEYBOARD_ACK);
95             PS2QueuePush(KbdPS2Port, KeyboardId);
96             break;
97         }
98 
99         /* Enable Reporting */
100         case 0xF4:
101         {
102             KeyboardReporting = TRUE;
103             PS2QueuePush(KbdPS2Port, KEYBOARD_ACK);
104             break;
105         }
106 
107         /* Disable Reporting */
108         case 0xF5:
109         {
110             KeyboardReporting = FALSE;
111             PS2QueuePush(KbdPS2Port, KEYBOARD_ACK);
112             break;
113         }
114 
115         /* Set Defaults */
116         case 0xF6:
117         {
118             // So far, nothing to reset
119             PS2QueuePush(KbdPS2Port, KEYBOARD_ACK);
120             break;
121         }
122 
123         /* PS/2 Typematic & Make/Break key modes */
124         case 0xF7: case 0xF8: case 0xF9:
125         case 0xFA: case 0xFB: case 0xFC: case 0xFD:
126         {
127             /*
128              * Unsupported on PC-AT, they are just ignored
129              * and acknowledged as discussed in:
130              * http://stanislavs.org/helppc/keyboard_commands.html
131              */
132             PS2QueuePush(KbdPS2Port, KEYBOARD_ACK);
133         }
134 
135         /* Resend */
136         case 0xFE:
137         {
138             PS2QueuePush(KbdPS2Port, KEYBOARD_ACK);
139             UNIMPLEMENTED;
140             break;
141         }
142 
143         /* Reset */
144         case 0xFF:
145         {
146             /* Send ACKnowledge */
147             PS2QueuePush(KbdPS2Port, KEYBOARD_ACK);
148 
149             // So far, nothing to reset
150 
151             /* Send the Basic Assurance Test success code and the device ID */
152             PS2QueuePush(KbdPS2Port, KEYBOARD_BAT_SUCCESS);
153             PS2QueuePush(KbdPS2Port, KeyboardId);
154             break;
155         }
156 
157         /* Unknown command */
158         default:
159         {
160             PS2QueuePush(KbdPS2Port, KEYBOARD_ERROR);
161         }
162     }
163 }
164 
165 /* PUBLIC FUNCTIONS ***********************************************************/
166 
167 VOID KeyboardEventHandler(PKEY_EVENT_RECORD KeyEvent)
168 {
169     WORD i;
170     BYTE ScanCode = (BYTE)KeyEvent->wVirtualScanCode;
171 
172     /* Check if we're not reporting */
173     if (!KeyboardReporting) return;
174 
175     /* If this is a key release, set the highest bit in the scan code */
176     if (!KeyEvent->bKeyDown) ScanCode |= 0x80;
177 
178     /* Push the scan code into the PS/2 queue */
179     for (i = 0; i < KeyEvent->wRepeatCount; i++)
180     {
181         if (KeyEvent->dwControlKeyState & ENHANCED_KEY) PS2QueuePush(KbdPS2Port, 0xE0);
182         PS2QueuePush(KbdPS2Port, ScanCode);
183     }
184 
185     DPRINT("Press 0x%X\n", ScanCode);
186 }
187 
188 BOOLEAN KeyboardInit(BYTE PS2Connector)
189 {
190     /* Finish to plug the keyboard to the specified PS/2 port */
191     KbdPS2Port = PS2Connector;
192     PS2SetDeviceCmdProc(KbdPS2Port, NULL, KeyboardCommand);
193     return TRUE;
194 }
195