0d91b7a5d867554bc9182c2b219de6de001055a9
[reactos.git] / subsystems / ntvdm / bios / bios.c
1 /*
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: bios.c
5 * PURPOSE: VDM BIOS
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 */
8
9 /* INCLUDES *******************************************************************/
10
11 #define NDEBUG
12
13 #include "emulator.h"
14 #include "bios.h"
15
16 #include "io.h"
17 #include "hardware/pic.h"
18 #include "hardware/ps2.h"
19 #include "hardware/timer.h"
20
21 #include "int32.h"
22 #include "registers.h"
23
24 /* PRIVATE VARIABLES **********************************************************/
25
26 PBIOS_DATA_AREA Bda;
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) return FALSE;
41
42 /* Put the value in the queue */
43 *((LPWORD)((ULONG_PTR)Bda + Bda->KeybdBufferTail)) = Data;
44 Bda->KeybdBufferTail += sizeof(WORD);
45
46 /* Check if we are at, or have passed, the end of the buffer */
47 if (Bda->KeybdBufferTail >= Bda->KeybdBufferEnd)
48 {
49 /* Return it to the beginning */
50 Bda->KeybdBufferTail = Bda->KeybdBufferStart;
51 }
52
53 /* Return success */
54 return TRUE;
55 }
56
57 static BOOLEAN BiosKbdBufferTop(LPWORD Data)
58 {
59 /* If it's empty, fail */
60 if (Bda->KeybdBufferHead == Bda->KeybdBufferTail) return FALSE;
61
62 /* Otherwise, get the value and return success */
63 *Data = *((LPWORD)((ULONG_PTR)Bda + Bda->KeybdBufferHead));
64
65 return TRUE;
66 }
67
68 static BOOLEAN BiosKbdBufferPop(VOID)
69 {
70 /* If it's empty, fail */
71 if (Bda->KeybdBufferHead == Bda->KeybdBufferTail) return FALSE;
72
73 /* Remove the value from the queue */
74 Bda->KeybdBufferHead += sizeof(WORD);
75
76 /* Check if we are at, or have passed, the end of the buffer */
77 if (Bda->KeybdBufferHead >= Bda->KeybdBufferEnd)
78 {
79 /* Return it to the beginning */
80 Bda->KeybdBufferHead = Bda->KeybdBufferStart;
81 }
82
83 /* Return success */
84 return TRUE;
85 }
86
87 static VOID WINAPI BiosEquipmentService(LPWORD Stack)
88 {
89 /* Return the equipment list */
90 setAX(Bda->EquipmentList);
91 }
92
93 static VOID WINAPI BiosGetMemorySize(LPWORD Stack)
94 {
95 /* Return the conventional memory size in kB, typically 640 kB */
96 setAX(Bda->MemorySize);
97 }
98
99 static VOID WINAPI BiosMiscService(LPWORD Stack)
100 {
101 switch (getAH())
102 {
103 /* Copy Extended Memory */
104 case 0x87:
105 {
106 DWORD Count = (DWORD)getCX() * 2;
107 PFAST486_GDT_ENTRY Gdt = (PFAST486_GDT_ENTRY)SEG_OFF_TO_PTR(getES(), getSI());
108 DWORD SourceBase = Gdt[2].Base + (Gdt[2].BaseMid << 16) + (Gdt[2].BaseHigh << 24);
109 DWORD SourceLimit = Gdt[2].Limit + (Gdt[2].LimitHigh << 16);
110 DWORD DestBase = Gdt[3].Base + (Gdt[3].BaseMid << 16) + (Gdt[3].BaseHigh << 24);
111 DWORD DestLimit = Gdt[3].Limit + (Gdt[3].LimitHigh << 16);
112
113 /* Check for flags */
114 if (Gdt[2].Granularity) SourceLimit = (SourceLimit << 12) | 0xFFF;
115 if (Gdt[3].Granularity) DestLimit = (DestLimit << 12) | 0xFFF;
116
117 if ((Count > SourceLimit) || (Count > DestLimit))
118 {
119 setAX(0x80);
120 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
121
122 break;
123 }
124
125 /* Copy */
126 RtlMoveMemory((PVOID)((ULONG_PTR)BaseAddress + DestBase),
127 (PVOID)((ULONG_PTR)BaseAddress + SourceBase),
128 Count);
129
130 setAX(ERROR_SUCCESS);
131 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
132 break;
133 }
134
135 /* Get Extended Memory Size */
136 case 0x88:
137 {
138 /* Return the number of KB of RAM after 1 MB */
139 setAX((MAX_ADDRESS - 0x100000) / 1024);
140
141 /* Clear CF */
142 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
143
144 break;
145 }
146
147 default:
148 {
149 DPRINT1("BIOS Function INT 15h, AH = 0x%02X NOT IMPLEMENTED\n",
150 getAH());
151 }
152 }
153 }
154
155 static VOID WINAPI BiosKeyboardService(LPWORD Stack)
156 {
157 switch (getAH())
158 {
159 /* Wait for keystroke and read */
160 case 0x00:
161 /* Wait for extended keystroke and read */
162 case 0x10: // FIXME: Temporarily do the same as INT 16h, 00h
163 {
164 /* Read the character (and wait if necessary) */
165 setAX(BiosGetCharacter());
166 break;
167 }
168
169 /* Get keystroke status */
170 case 0x01:
171 /* Get extended keystroke status */
172 case 0x11: // FIXME: Temporarily do the same as INT 16h, 01h
173 {
174 WORD Data = BiosPeekCharacter();
175
176 if (Data != 0xFFFF)
177 {
178 /* There is a character, clear ZF and return it */
179 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_ZF;
180 setAX(Data);
181 }
182 else
183 {
184 /* No character, set ZF */
185 Stack[STACK_FLAGS] |= EMULATOR_FLAG_ZF;
186 }
187
188 break;
189 }
190
191 /* Get shift status */
192 case 0x02:
193 {
194 /* Return the lower byte of the keyboard shift status word */
195 setAL(LOBYTE(Bda->KeybdShiftFlags));
196 break;
197 }
198
199 /* Reserved */
200 case 0x04:
201 {
202 DPRINT1("BIOS Function INT 16h, AH = 0x04 is RESERVED\n");
203 break;
204 }
205
206 /* Push keystroke */
207 case 0x05:
208 {
209 /* Return 0 if success, 1 if failure */
210 setAL(BiosKbdBufferPush(getCX()) == FALSE);
211 break;
212 }
213
214 /* Get extended shift status */
215 case 0x12:
216 {
217 /*
218 * Be careful! The returned word is similar to Bda->KeybdShiftFlags
219 * but the high byte is organized differently:
220 * the bytes 2 and 3 of the high byte are not the same...
221 */
222 WORD KeybdShiftFlags = (Bda->KeybdShiftFlags & 0xF3FF);
223
224 /* Return the extended keyboard shift status word */
225 setAX(KeybdShiftFlags);
226 break;
227 }
228
229 default:
230 {
231 DPRINT1("BIOS Function INT 16h, AH = 0x%02X NOT IMPLEMENTED\n",
232 getAH());
233 }
234 }
235 }
236
237 static VOID WINAPI BiosTimeService(LPWORD Stack)
238 {
239 switch (getAH())
240 {
241 case 0x00:
242 {
243 /* Set AL to 1 if midnight had passed, 0 otherwise */
244 setAL(Bda->MidnightPassed ? 0x01 : 0x00);
245
246 /* Return the tick count in CX:DX */
247 setCX(HIWORD(Bda->TickCounter));
248 setDX(LOWORD(Bda->TickCounter));
249
250 /* Reset the midnight flag */
251 Bda->MidnightPassed = FALSE;
252
253 break;
254 }
255
256 case 0x01:
257 {
258 /* Set the tick count to CX:DX */
259 Bda->TickCounter = MAKELONG(getDX(), getCX());
260
261 /* Reset the midnight flag */
262 Bda->MidnightPassed = FALSE;
263
264 break;
265 }
266
267 default:
268 {
269 DPRINT1("BIOS Function INT 1Ah, AH = 0x%02X NOT IMPLEMENTED\n",
270 getAH());
271 }
272 }
273 }
274
275 static VOID WINAPI BiosSystemTimerInterrupt(LPWORD Stack)
276 {
277 /* Increase the system tick count */
278 Bda->TickCounter++;
279 }
280
281 /* PUBLIC FUNCTIONS ***********************************************************/
282
283 WORD BiosPeekCharacter(VOID)
284 {
285 WORD CharacterData = 0;
286
287 /* Get the key from the queue, but don't remove it */
288 if (BiosKbdBufferTop(&CharacterData)) return CharacterData;
289 else return 0xFFFF;
290 }
291
292 WORD BiosGetCharacter(VOID)
293 {
294 WORD CharacterData = 0;
295
296 /* Check if there is a key available */
297 if (BiosKbdBufferTop(&CharacterData))
298 {
299 /* A key was available, remove it from the queue */
300 BiosKbdBufferPop();
301 }
302 else
303 {
304 /* No key available. Set the handler CF to repeat the BOP */
305 setCF(1);
306 // CharacterData = 0xFFFF;
307 }
308
309 return CharacterData;
310 }
311
312 BOOLEAN BiosInitialize(HANDLE ConsoleInput, HANDLE ConsoleOutput)
313 {
314 /* Initialize the BDA */
315 Bda = (PBIOS_DATA_AREA)SEG_OFF_TO_PTR(BDA_SEGMENT, 0);
316 Bda->EquipmentList = BIOS_EQUIPMENT_LIST;
317 /*
318 * Conventional memory size is 640 kB,
319 * see: http://webpages.charter.net/danrollins/techhelp/0184.HTM
320 * and see Ralf Brown: http://www.ctyme.com/intr/rb-0598.htm
321 * for more information.
322 */
323 Bda->MemorySize = 0x0280;
324 Bda->KeybdBufferStart = FIELD_OFFSET(BIOS_DATA_AREA, KeybdBuffer);
325 Bda->KeybdBufferEnd = Bda->KeybdBufferStart + BIOS_KBD_BUFFER_SIZE * sizeof(WORD);
326 Bda->KeybdBufferHead = Bda->KeybdBufferTail = 0;
327
328 /* Initialize the 32-bit Interrupt system */
329 InitializeInt32(BIOS_SEGMENT);
330
331 /* Register the BIOS 32-bit Interrupts */
332 RegisterInt32(BIOS_EQUIPMENT_INTERRUPT, BiosEquipmentService );
333 RegisterInt32(BIOS_MEMORY_SIZE , BiosGetMemorySize );
334 RegisterInt32(BIOS_MISC_INTERRUPT , BiosMiscService );
335 RegisterInt32(BIOS_KBD_INTERRUPT , BiosKeyboardService );
336 RegisterInt32(BIOS_TIME_INTERRUPT , BiosTimeService );
337 RegisterInt32(BIOS_SYS_TIMER_INTERRUPT, BiosSystemTimerInterrupt);
338
339 /* Some interrupts are in fact addresses to tables */
340 ((PDWORD)BaseAddress)[0x1D] = (DWORD)NULL;
341 ((PDWORD)BaseAddress)[0x1E] = (DWORD)NULL;
342 ((PDWORD)BaseAddress)[0x1F] = (DWORD)NULL;
343
344 ((PDWORD)BaseAddress)[0x41] = (DWORD)NULL;
345 ((PDWORD)BaseAddress)[0x43] = (DWORD)NULL;
346 ((PDWORD)BaseAddress)[0x44] = (DWORD)NULL;
347 ((PDWORD)BaseAddress)[0x46] = (DWORD)NULL;
348 ((PDWORD)BaseAddress)[0x48] = (DWORD)NULL;
349 ((PDWORD)BaseAddress)[0x49] = (DWORD)NULL;
350
351 /* Initialize the Video BIOS */
352 if (!VidBiosInitialize(ConsoleOutput)) return FALSE;
353
354 /* Set the console input mode */
355 SetConsoleMode(ConsoleInput, ENABLE_MOUSE_INPUT | ENABLE_PROCESSED_INPUT);
356
357 /* Initialize PS2 */
358 PS2Initialize(ConsoleInput);
359
360 /*
361 * The POST (Power On-Self Test)
362 */
363
364 /* Initialize the PIC */
365 IOWriteB(PIC_MASTER_CMD, PIC_ICW1 | PIC_ICW1_ICW4);
366 IOWriteB(PIC_SLAVE_CMD , PIC_ICW1 | PIC_ICW1_ICW4);
367
368 /* Set the interrupt offsets */
369 IOWriteB(PIC_MASTER_DATA, BIOS_PIC_MASTER_INT);
370 IOWriteB(PIC_SLAVE_DATA , BIOS_PIC_SLAVE_INT);
371
372 /* Tell the master PIC there is a slave at IRQ 2 */
373 IOWriteB(PIC_MASTER_DATA, 1 << 2);
374 IOWriteB(PIC_SLAVE_DATA , 2);
375
376 /* Make sure the PIC is in 8086 mode */
377 IOWriteB(PIC_MASTER_DATA, PIC_ICW4_8086);
378 IOWriteB(PIC_SLAVE_DATA , PIC_ICW4_8086);
379
380 /* Clear the masks for both PICs */
381 IOWriteB(PIC_MASTER_DATA, 0x00);
382 IOWriteB(PIC_SLAVE_DATA , 0x00);
383
384 PitWriteCommand(0x34);
385 PitWriteData(0, 0x00);
386 PitWriteData(0, 0x00);
387
388 return TRUE;
389 }
390
391 VOID BiosCleanup(VOID)
392 {
393 PS2Cleanup();
394 VidBiosCleanup();
395 }
396
397 VOID BiosHandleIrq(BYTE IrqNumber, LPWORD Stack)
398 {
399 switch (IrqNumber)
400 {
401 /* PIT IRQ */
402 case 0:
403 {
404 /* Perform the system timer interrupt */
405 EmulatorInterrupt(BIOS_SYS_TIMER_INTERRUPT);
406 break;
407 }
408
409 /* Keyboard IRQ */
410 case 1:
411 {
412 BYTE ScanCode, VirtualKey;
413 WORD Character;
414
415 /* Get the scan code and virtual key code */
416 ScanCode = IOReadB(PS2_DATA_PORT);
417 VirtualKey = MapVirtualKey(ScanCode & 0x7F, MAPVK_VSC_TO_VK);
418
419 /* Check if this is a key press or release */
420 if (!(ScanCode & (1 << 7)))
421 {
422 /* Key press */
423 if (VirtualKey == VK_NUMLOCK ||
424 VirtualKey == VK_CAPITAL ||
425 VirtualKey == VK_SCROLL ||
426 VirtualKey == VK_INSERT)
427 {
428 /* For toggle keys, toggle the lowest bit in the keyboard map */
429 BiosKeyboardMap[VirtualKey] ^= ~(1 << 0);
430 }
431
432 /* Set the highest bit */
433 BiosKeyboardMap[VirtualKey] |= (1 << 7);
434
435 /* Find out which character this is */
436 Character = 0;
437 if (ToAscii(VirtualKey, ScanCode, BiosKeyboardMap, &Character, 0) == 0)
438 {
439 /* Not ASCII */
440 Character = 0;
441 }
442
443 /* Push it onto the BIOS keyboard queue */
444 BiosKbdBufferPush(MAKEWORD(Character, ScanCode));
445 }
446 else
447 {
448 /* Key release, unset the highest bit */
449 BiosKeyboardMap[VirtualKey] &= ~(1 << 7);
450 }
451
452 /* Clear the keyboard flags */
453 Bda->KeybdShiftFlags = 0;
454
455 /* Set the appropriate flags based on the state */
456 if (BiosKeyboardMap[VK_RSHIFT] & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_RSHIFT;
457 if (BiosKeyboardMap[VK_LSHIFT] & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_LSHIFT;
458 if (BiosKeyboardMap[VK_CONTROL] & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_CTRL;
459 if (BiosKeyboardMap[VK_MENU] & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_ALT;
460 if (BiosKeyboardMap[VK_SCROLL] & (1 << 0)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_SCROLL_ON;
461 if (BiosKeyboardMap[VK_NUMLOCK] & (1 << 0)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_NUMLOCK_ON;
462 if (BiosKeyboardMap[VK_CAPITAL] & (1 << 0)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_CAPSLOCK_ON;
463 if (BiosKeyboardMap[VK_INSERT] & (1 << 0)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_INSERT_ON;
464 if (BiosKeyboardMap[VK_RMENU] & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_RALT;
465 if (BiosKeyboardMap[VK_LMENU] & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_LALT;
466 if (BiosKeyboardMap[VK_SNAPSHOT] & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_SYSRQ;
467 if (BiosKeyboardMap[VK_PAUSE] & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_PAUSE;
468 if (BiosKeyboardMap[VK_SCROLL] & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_SCROLL;
469 if (BiosKeyboardMap[VK_NUMLOCK] & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_NUMLOCK;
470 if (BiosKeyboardMap[VK_CAPITAL] & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_CAPSLOCK;
471 if (BiosKeyboardMap[VK_INSERT] & (1 << 7)) Bda->KeybdShiftFlags |= BDA_KBDFLAG_INSERT;
472
473 break;
474 }
475 }
476
477 /* Send End-of-Interrupt to the PIC */
478 if (IrqNumber >= 8) IOWriteB(PIC_SLAVE_CMD, PIC_OCW2_EOI);
479 IOWriteB(PIC_MASTER_CMD, PIC_OCW2_EOI);
480 }
481
482 /* EOF */