1 /*
2  * COPYRIGHT:       GPL - See COPYING in the top level directory
3  * PROJECT:         ReactOS Virtual DOS Machine
4  * FILE:            subsystems/mvdm/ntvdm/bios/bios32/kbdbios32.c
5  * PURPOSE:         VDM 32-bit PS/2 Keyboard BIOS
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 "kbdbios32.h"
17 #include <bios/kbdbios.h>
18 #include "bios32p.h"
19 
20 #include "int32.h"
21 #include "cpu/cpu.h" // for EMULATOR_FLAG_ZF
22 #include "io.h"
23 #include "hardware/ps2.h"
24 
25 /* PRIVATE VARIABLES **********************************************************/
26 
27 static BYTE BiosKeyboardMap[256];
28 
29 /* PRIVATE FUNCTIONS **********************************************************/
30 
31 static BOOLEAN BiosKbdBufferPush(WORD Data)
32 {
33     /* Get the location of the element after the tail */
34     WORD NextElement = Bda->KeybdBufferTail + sizeof(WORD);
35 
36     /* Wrap it around if it's at or beyond the end */
37     if (NextElement >= Bda->KeybdBufferEnd) NextElement = Bda->KeybdBufferStart;
38 
39     /* If it's full, fail */
40     if (NextElement == Bda->KeybdBufferHead)
41     {
42         DPRINT1("BIOS keyboard buffer full.\n");
43         return FALSE;
44     }
45 
46     /* Put the value in the queue */
47     *((LPWORD)((ULONG_PTR)Bda + Bda->KeybdBufferTail)) = Data;
48     Bda->KeybdBufferTail = NextElement;
49 
50     /* Return success */
51     return TRUE;
52 }
53 
54 static BOOLEAN BiosKbdBufferTop(LPWORD Data)
55 {
56     /* If it's empty, fail */
57     if (Bda->KeybdBufferHead == Bda->KeybdBufferTail) return FALSE;
58 
59     /* Otherwise, get the value and return success */
60     *Data = *((LPWORD)((ULONG_PTR)Bda + Bda->KeybdBufferHead));
61 
62     return TRUE;
63 }
64 
65 static BOOLEAN BiosKbdBufferPop(VOID)
66 {
67     /* If it's empty, fail */
68     if (Bda->KeybdBufferHead == Bda->KeybdBufferTail) return FALSE;
69 
70     /* Remove the value from the queue */
71     Bda->KeybdBufferHead += sizeof(WORD);
72 
73     /* Check if we are at, or have passed, the end of the buffer */
74     if (Bda->KeybdBufferHead >= Bda->KeybdBufferEnd)
75     {
76         /* Return it to the beginning */
77         Bda->KeybdBufferHead = Bda->KeybdBufferStart;
78     }
79 
80     /* Return success */
81     return TRUE;
82 }
83 
84 /* static */
85 VOID WINAPI BiosKeyboardService(LPWORD Stack)
86 {
87     switch (getAH())
88     {
89         /* Wait for keystroke and read */
90         case 0x00:
91         /* Wait for extended keystroke and read */
92         case 0x10:
93         {
94             WORD Character;
95 
96             /* Read the character (and wait if necessary) */
97             if (!BiosKbdBufferTop(&Character))
98             {
99                 /* No key available. Set the handler CF to repeat the BOP */
100                 setCF(1);
101                 break;
102             }
103 
104             if (getAH() == 0x00 && LOBYTE(Character) == 0xE0)
105             {
106                 /* Clear the extended code */
107                 Character &= 0xFF00;
108             }
109 
110             BiosKbdBufferPop();
111             setAX(Character);
112             setCF(0);
113 
114             break;
115         }
116 
117         /* Get keystroke status */
118         case 0x01:
119         /* Get extended keystroke status */
120         case 0x11:
121         {
122             WORD Character;
123 
124             if (BiosKbdBufferTop(&Character))
125             {
126                 /* There is a character, clear ZF and return it */
127                 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_ZF;
128 
129                 if (getAH() == 0x01 && LOBYTE(Character) == 0xE0)
130                 {
131                     /* Clear the extended code */
132                     Character &= 0xFF00;
133                 }
134 
135                 setAX(Character);
136             }
137             else
138             {
139                 /* No character, set ZF */
140                 Stack[STACK_FLAGS] |= EMULATOR_FLAG_ZF;
141             }
142 
143             break;
144         }
145 
146         /* Get shift status */
147         case 0x02:
148         {
149             /* Return the lower byte of the keyboard shift status word */
150             setAL(LOBYTE(Bda->KeybdShiftFlags));
151             break;
152         }
153 
154         /* Reserved */
155         case 0x04:
156         {
157             DPRINT1("BIOS Function INT 16h, AH = 0x04 is RESERVED\n");
158             break;
159         }
160 
161         /* Push keystroke */
162         case 0x05:
163         {
164             /* Return 0 if success, 1 if failure */
165             setAL(BiosKbdBufferPush(getCX()) == FALSE);
166             break;
167         }
168 
169         /* Get extended shift status */
170         case 0x12:
171         {
172             /*
173              * Be careful! The returned word is similar to 'Bda->KeybdShiftFlags'
174              * but the high byte is organized differently:
175              * the bits 2 and 3 of the high byte are not the same:
176              * instead they correspond to the right CTRL and ALT keys as specified
177              * in bits 2 and 3 of LOBYTE(Bda->KeybdStatusFlags).
178              */
179             // Bda->KeybdShiftFlags & 0xF3FF;
180             WORD KeybdShiftFlags = MAKEWORD(LOBYTE(Bda->KeybdShiftFlags),
181                                                (HIBYTE(Bda->KeybdShiftFlags ) & 0xF3) |
182                                                (LOBYTE(Bda->KeybdStatusFlags) & 0x0C));
183 
184             /* Return the extended keyboard shift status word */
185             setAX(KeybdShiftFlags);
186             break;
187         }
188 
189         default:
190         {
191             DPRINT1("BIOS Function INT 16h, AH = 0x%02X NOT IMPLEMENTED\n",
192                     getAH());
193         }
194     }
195 }
196 
197 // Keyboard IRQ 1
198 /* static */
199 VOID WINAPI BiosKeyboardIrq(LPWORD Stack)
200 {
201     static BOOLEAN Extended = FALSE;
202     BOOLEAN SkipScanCode;
203     BYTE ScanCode, VirtualKey;
204     WORD Character;
205 
206     /*
207      * Get the scan code from the PS/2 port, then call the
208      * INT 15h, AH=4Fh Keyboard Intercept function to try to
209      * translate the scan code. CF must be set before the call.
210      * In return, if CF is set we continue processing the scan code
211      * stored in AL, and if not, we skip it.
212      */
213     WORD AX = getAX();
214     WORD CX = getCX();
215     WORD DX = getDX();
216     WORD BX = getBX();
217     WORD BP = getBP();
218     WORD SI = getSI();
219     WORD DI = getDI();
220     WORD DS = getDS();
221     WORD ES = getES();
222 
223     setCF(1);
224     setAL(IOReadB(PS2_DATA_PORT));
225     setAH(0x4F);
226     Int32Call(&BiosContext, BIOS_MISC_INTERRUPT);
227 
228     /* Retrieve the modified scan code in AL */
229     SkipScanCode = (getCF() == 0);
230     ScanCode = getAL();
231 
232     setAX(AX);
233     setCX(CX);
234     setDX(DX);
235     setBX(BX);
236     setBP(BP);
237     setSI(SI);
238     setDI(DI);
239     setDS(DS);
240     setES(ES);
241     setCF(0);
242 
243     if (ScanCode == 0xE0)
244     {
245         Extended = TRUE;
246         Bda->KeybdStatusFlags |= 0x02;
247         goto Quit;
248     }
249 
250     // FIXME: For diagnostic purposes. We should decide what to do then!!
251     if (ScanCode == 0xE1)
252         DPRINT1("BiosKeyboardIrq, ScanCode == 0xE1\n");
253 
254     /* Check whether CF is clear. If so, skip the scan code. */
255     if (SkipScanCode) goto Quit;
256 
257     /* Get the corresponding virtual key code */
258     VirtualKey = MapVirtualKey(ScanCode & 0x7F, MAPVK_VSC_TO_VK);
259 
260     /* Check if this is a key press or release */
261     if (!(ScanCode & (1 << 7)))
262     {
263         /* Key press, set the highest bit */
264         BiosKeyboardMap[VirtualKey] |= (1 << 7);
265 
266         switch (VirtualKey)
267         {
268             case VK_NUMLOCK:
269             case VK_CAPITAL:
270             case VK_SCROLL:
271             case VK_INSERT:
272             {
273                 /* For toggle keys, toggle the lowest bit in the keyboard map */
274                 BiosKeyboardMap[VirtualKey] ^= ~(1 << 0);
275                 break;
276             }
277 
278             case VK_SHIFT:
279             case VK_LSHIFT:
280             case VK_RSHIFT:
281             case VK_CONTROL:
282             case VK_RCONTROL:
283             case VK_LCONTROL:
284             case VK_MENU:
285             case VK_LMENU:
286             case VK_RMENU:
287             {
288                 /* Modifier keys don't go in the buffer */
289                 break;
290             }
291 
292             default:
293             {
294                 Character = Extended ? 0xE0 : 0x00;
295 
296                 /* If this is not an extended scancode, and ALT isn't held down, find out which character this is */
297                 if (!Extended && !(Bda->KeybdShiftFlags & (BDA_KBDFLAG_ALT | BDA_KBDFLAG_LALT | BDA_KBDFLAG_RALT)))
298                 {
299                     if (ToAscii(VirtualKey, ScanCode, BiosKeyboardMap, &Character, 0) == 0)
300                     {
301                         /* Not ASCII */
302                         Character = 0;
303                     }
304                 }
305 
306                 /* Push it onto the BIOS keyboard queue */
307                 BiosKbdBufferPush(MAKEWORD(Character, ScanCode));
308             }
309         }
310     }
311     else
312     {
313         /* Key release, unset the highest bit */
314         BiosKeyboardMap[VirtualKey] &= ~(1 << 7);
315     }
316 
317     /* Clear the keyboard flags */
318     Bda->KeybdShiftFlags = 0;
319     // Release right CTRL and ALT keys
320     Bda->KeybdStatusFlags &= ~(BDA_KBDFLAG_RCTRL | BDA_KBDFLAG_RALT);
321 
322     /* Set the appropriate flags based on the state */
323     // SHIFT
324     if (BiosKeyboardMap[VK_RSHIFT]   & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_RSHIFT;
325     if (BiosKeyboardMap[VK_LSHIFT]   & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_LSHIFT;
326     if (BiosKeyboardMap[VK_SHIFT]    & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_LSHIFT;
327     // CTRL
328     if (BiosKeyboardMap[VK_RCONTROL] & (1 << 7)) Bda->KeybdStatusFlags |= BDA_KBDFLAG_RCTRL;
329     if (BiosKeyboardMap[VK_LCONTROL] & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_LCTRL;
330     if (BiosKeyboardMap[VK_CONTROL]  & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_CTRL;
331     // ALT
332     if (BiosKeyboardMap[VK_RMENU]    & (1 << 7)) Bda->KeybdStatusFlags |= BDA_KBDFLAG_RALT;
333     if (BiosKeyboardMap[VK_LMENU]    & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_LALT;
334     if (BiosKeyboardMap[VK_MENU]     & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_ALT;
335     // Others
336     if (BiosKeyboardMap[VK_SCROLL]   & (1 << 0)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_SCROLL_ON;
337     if (BiosKeyboardMap[VK_NUMLOCK]  & (1 << 0)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_NUMLOCK_ON;
338     if (BiosKeyboardMap[VK_CAPITAL]  & (1 << 0)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_CAPSLOCK_ON;
339     if (BiosKeyboardMap[VK_INSERT]   & (1 << 0)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_INSERT_ON;
340     if (BiosKeyboardMap[VK_SNAPSHOT] & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_SYSRQ;
341     if (BiosKeyboardMap[VK_PAUSE]    & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_PAUSE;
342     if (BiosKeyboardMap[VK_SCROLL]   & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_SCROLL;
343     if (BiosKeyboardMap[VK_NUMLOCK]  & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_NUMLOCK;
344     if (BiosKeyboardMap[VK_CAPITAL]  & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_CAPSLOCK;
345     if (BiosKeyboardMap[VK_INSERT]   & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_INSERT;
346 
347     /* Clear the extended key flag */
348     Extended = FALSE;
349     Bda->KeybdStatusFlags &= ~0x02; // Remove the 0xE0 code flag
350     // Bda->KeybdStatusFlags &= ~0x01; // Remove the 0xE1 code flag
351 
352     DPRINT("BiosKeyboardIrq - Character = 0x%X, ScanCode = 0x%X, KeybdShiftFlags = 0x%X\n",
353            Character, ScanCode, Bda->KeybdShiftFlags);
354 
355 Quit:
356     PicIRQComplete(LOBYTE(Stack[STACK_INT_NUM]));
357 }
358 
359 /* PUBLIC FUNCTIONS ***********************************************************/
360 
361 VOID KbdBios32Post(VOID)
362 {
363     /* Initialize the BDA */
364     Bda->KeybdBufferStart = FIELD_OFFSET(BIOS_DATA_AREA, KeybdBuffer);
365     Bda->KeybdBufferEnd   = Bda->KeybdBufferStart + BIOS_KBD_BUFFER_SIZE * sizeof(WORD);
366     Bda->KeybdBufferHead  = Bda->KeybdBufferTail = Bda->KeybdBufferStart;
367 
368     // FIXME: Fill the keyboard buffer with invalid values for diagnostic purposes...
369     RtlFillMemory(((LPVOID)((ULONG_PTR)Bda + Bda->KeybdBufferStart)),
370                   BIOS_KBD_BUFFER_SIZE * sizeof(WORD), 'A');
371 
372     Bda->KeybdShiftFlags  = 0;
373     Bda->KeybdStatusFlags = (1 << 4); // 101/102 enhanced keyboard installed
374     Bda->KeybdLedFlags    = 0;
375 
376     /*
377      * Register the BIOS 32-bit Interrupts:
378      * - Software vector handler
379      * - HW vector interrupt
380      */
381     RegisterBiosInt32(BIOS_KBD_INTERRUPT, BiosKeyboardService);
382     EnableHwIRQ(1, BiosKeyboardIrq);
383 }
384 
385 /* EOF */
386