[NTVDM]
[reactos.git] / subsystems / ntvdm / 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 #include "bios.h"
12 #include "emulator.h"
13 #include "pic.h"
14 #include "ps2.h"
15 #include "timer.h"
16
17 /* PRIVATE VARIABLES **********************************************************/
18
19 static BYTE CursorRow, CursorCol;
20 static WORD ConsoleWidth, ConsoleHeight;
21 static BYTE BiosKeyboardMap[256];
22 static WORD BiosKbdBuffer[BIOS_KBD_BUFFER_SIZE];
23 static UINT BiosKbdBufferStart = 0, BiosKbdBufferEnd = 0;
24 static BOOLEAN BiosKbdBufferEmpty = TRUE;
25 static DWORD BiosTickCount = 0;
26 static BOOLEAN BiosPassedMidnight = FALSE;
27
28 /* PRIVATE FUNCTIONS **********************************************************/
29
30 static COORD BiosVideoAddressToCoord(ULONG Address)
31 {
32 COORD Result = {0, 0};
33 CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo;
34 HANDLE ConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
35
36 if (!GetConsoleScreenBufferInfo(ConsoleOutput, &ConsoleInfo))
37 {
38 ASSERT(FALSE);
39 return Result;
40 }
41
42 Result.X = ((Address - CONSOLE_VIDEO_MEM_START) >> 1) % ConsoleInfo.dwSize.X;
43 Result.Y = ((Address - CONSOLE_VIDEO_MEM_START) >> 1) / ConsoleInfo.dwSize.X;
44
45 return Result;
46 }
47
48 static BOOLEAN BiosKbdBufferPush(WORD Data)
49 {
50 /* If it's full, fail */
51 if (!BiosKbdBufferEmpty && (BiosKbdBufferStart == BiosKbdBufferEnd))
52 {
53 return FALSE;
54 }
55
56 /* Otherwise, add the value to the queue */
57 BiosKbdBuffer[BiosKbdBufferEnd] = Data;
58 BiosKbdBufferEnd++;
59 BiosKbdBufferEnd %= BIOS_KBD_BUFFER_SIZE;
60 BiosKbdBufferEmpty = FALSE;
61
62 /* Return success */
63 return TRUE;
64 }
65
66 static BOOLEAN BiosKbdBufferTop(LPWORD Data)
67 {
68 /* If it's empty, fail */
69 if (BiosKbdBufferEmpty) return FALSE;
70
71 /* Otherwise, get the value and return success */
72 *Data = BiosKbdBuffer[BiosKbdBufferStart];
73 return TRUE;
74 }
75
76 static BOOLEAN BiosKbdBufferPop()
77 {
78 /* If it's empty, fail */
79 if (BiosKbdBufferEmpty) return FALSE;
80
81 /* Otherwise, remove the value and return success */
82 BiosKbdBufferStart++;
83 BiosKbdBufferStart %= BIOS_KBD_BUFFER_SIZE;
84 if (BiosKbdBufferStart == BiosKbdBufferEnd) BiosKbdBufferEmpty = TRUE;
85
86 return TRUE;
87 }
88
89 /* PUBLIC FUNCTIONS ***********************************************************/
90
91 BOOLEAN BiosInitialize()
92 {
93 INT i;
94 WORD Offset = 0;
95 HANDLE ConsoleInput = GetStdHandle(STD_INPUT_HANDLE);
96 HANDLE ConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
97 CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo;
98 LPWORD IntVecTable = (LPWORD)((ULONG_PTR)BaseAddress);
99 LPBYTE BiosCode = (LPBYTE)((ULONG_PTR)BaseAddress + TO_LINEAR(BIOS_SEGMENT, 0));
100
101 /* Generate ISR stubs and fill the IVT */
102 for (i = 0; i < 256; i++)
103 {
104 IntVecTable[i * 2] = Offset;
105 IntVecTable[i * 2 + 1] = BIOS_SEGMENT;
106
107 if (i != SPECIAL_INT_NUM)
108 {
109 BiosCode[Offset++] = 0xFA; // cli
110
111 BiosCode[Offset++] = 0x6A; // push i
112 BiosCode[Offset++] = (BYTE)i;
113
114 BiosCode[Offset++] = 0xCD; // int SPECIAL_INT_NUM
115 BiosCode[Offset++] = SPECIAL_INT_NUM;
116
117 BiosCode[Offset++] = 0x83; // add sp, 2
118 BiosCode[Offset++] = 0xC4;
119 BiosCode[Offset++] = 0x02;
120 }
121
122 BiosCode[Offset++] = 0xCF; // iret
123 }
124
125 /* Get the console buffer info */
126 if (!GetConsoleScreenBufferInfo(ConsoleOutput, &ConsoleInfo))
127 {
128 return FALSE;
129 }
130
131 /* Set the initial cursor position and console size */
132 CursorCol = ConsoleInfo.dwCursorPosition.X;
133 CursorRow = ConsoleInfo.dwCursorPosition.Y;
134 ConsoleWidth = ConsoleInfo.dwSize.X;
135 ConsoleHeight = ConsoleInfo.dwSize.Y;
136
137 /* Set the console input mode */
138 SetConsoleMode(ConsoleInput, ENABLE_MOUSE_INPUT | ENABLE_PROCESSED_INPUT);
139
140 /* Initialize the PIC */
141 PicWriteCommand(PIC_MASTER_CMD, PIC_ICW1 | PIC_ICW1_ICW4);
142 PicWriteCommand(PIC_SLAVE_CMD, PIC_ICW1 | PIC_ICW1_ICW4);
143
144 /* Set the interrupt offsets */
145 PicWriteData(PIC_MASTER_DATA, BIOS_PIC_MASTER_INT);
146 PicWriteData(PIC_SLAVE_DATA, BIOS_PIC_SLAVE_INT);
147
148 /* Tell the master PIC there is a slave at IRQ 2 */
149 PicWriteData(PIC_MASTER_DATA, 1 << 2);
150 PicWriteData(PIC_SLAVE_DATA, 2);
151
152 /* Make sure the PIC is in 8086 mode */
153 PicWriteData(PIC_MASTER_DATA, PIC_ICW4_8086);
154 PicWriteData(PIC_SLAVE_DATA, PIC_ICW4_8086);
155
156 /* Clear the masks for both PICs */
157 PicWriteData(PIC_MASTER_DATA, 0x00);
158 PicWriteData(PIC_SLAVE_DATA, 0x00);
159
160 PitWriteCommand(0x34);
161 PitWriteData(0, 0x00);
162 PitWriteData(0, 0x00);
163
164 return TRUE;
165 }
166
167 VOID BiosUpdateConsole(ULONG StartAddress, ULONG EndAddress)
168 {
169 ULONG i;
170 COORD Coordinates;
171 DWORD CharsWritten;
172 HANDLE ConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
173
174 /* Loop through all the addresses */
175 for (i = StartAddress; i < EndAddress; i++)
176 {
177 /* Get the coordinates */
178 Coordinates = BiosVideoAddressToCoord(i);
179
180 /* Check if this is a character byte or an attribute byte */
181 if ((i - CONSOLE_VIDEO_MEM_START) % 2 == 0)
182 {
183 /* This is a regular character */
184 FillConsoleOutputCharacterA(ConsoleOutput,
185 *(PCHAR)((ULONG_PTR)BaseAddress + i),
186 sizeof(CHAR),
187 Coordinates,
188 &CharsWritten);
189 }
190 else
191 {
192 /* This is an attribute */
193 FillConsoleOutputAttribute(ConsoleOutput,
194 *(PCHAR)((ULONG_PTR)BaseAddress + i),
195 sizeof(CHAR),
196 Coordinates,
197 &CharsWritten);
198 }
199 }
200 }
201
202 VOID BiosUpdateVideoMemory(ULONG StartAddress, ULONG EndAddress)
203 {
204 ULONG i;
205 COORD Coordinates;
206 WORD Attribute;
207 DWORD CharsWritten;
208 HANDLE ConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
209
210 /* Loop through all the addresses */
211 for (i = StartAddress; i < EndAddress; i++)
212 {
213 /* Get the coordinates */
214 Coordinates = BiosVideoAddressToCoord(i);
215
216 /* Check if this is a character byte or an attribute byte */
217 if ((i - CONSOLE_VIDEO_MEM_START) % 2 == 0)
218 {
219 /* This is a regular character */
220 ReadConsoleOutputCharacterA(ConsoleOutput,
221 (LPSTR)((ULONG_PTR)BaseAddress + i),
222 sizeof(CHAR),
223 Coordinates,
224 &CharsWritten);
225 }
226 else
227 {
228 /* This is an attribute */
229 ReadConsoleOutputAttribute(ConsoleOutput,
230 &Attribute,
231 sizeof(CHAR),
232 Coordinates,
233 &CharsWritten);
234
235 *(PCHAR)((ULONG_PTR)BaseAddress + i) = LOBYTE(Attribute);
236 }
237 }
238 }
239
240 WORD BiosPeekCharacter()
241 {
242 WORD CharacterData;
243
244 /* Check if there is a key available */
245 if (BiosKbdBufferEmpty) return 0xFFFF;
246
247 /* Get the key from the queue, but don't remove it */
248 BiosKbdBufferTop(&CharacterData);
249
250 return CharacterData;
251 }
252
253 WORD BiosGetCharacter()
254 {
255 WORD CharacterData;
256 HANDLE ConsoleInput = GetStdHandle(STD_INPUT_HANDLE);
257 INPUT_RECORD InputRecord;
258 DWORD Count;
259
260 /* Check if there is a key available */
261 if (!BiosKbdBufferEmpty)
262 {
263 /* Get the key from the queue, and remove it */
264 BiosKbdBufferTop(&CharacterData);
265 BiosKbdBufferPop();
266 }
267 else
268 {
269 while (TRUE)
270 {
271 /* Wait for a console event */
272 WaitForSingleObject(ConsoleInput, INFINITE);
273
274 /* Read the event, and make sure it's a keypress */
275 if (!ReadConsoleInput(ConsoleInput, &InputRecord, 1, &Count)) continue;
276 if (InputRecord.EventType != KEY_EVENT) continue;
277 if (!InputRecord.Event.KeyEvent.bKeyDown) continue;
278
279 /* Save the scan code and end the loop */
280 CharacterData = (InputRecord.Event.KeyEvent.wVirtualScanCode << 8)
281 | InputRecord.Event.KeyEvent.uChar.AsciiChar;
282
283 break;
284 }
285 }
286
287 return CharacterData;
288 }
289
290 VOID BiosVideoService()
291 {
292 HANDLE ConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
293 INT CursorHeight;
294 BOOLEAN Invisible = FALSE;
295 COORD Position;
296 CONSOLE_CURSOR_INFO CursorInfo;
297 CHAR_INFO Character;
298 SMALL_RECT Rect;
299 DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
300 DWORD Ecx = EmulatorGetRegister(EMULATOR_REG_CX);
301 DWORD Edx = EmulatorGetRegister(EMULATOR_REG_DX);
302 DWORD Ebx = EmulatorGetRegister(EMULATOR_REG_BX);
303
304 switch (HIBYTE(Eax))
305 {
306 /* Set Text-Mode Cursor Shape */
307 case 0x01:
308 {
309 /* Retrieve and validate the input */
310 Invisible = ((HIBYTE(Ecx) >> 5) & 0x03) ? TRUE : FALSE;
311 CursorHeight = (HIBYTE(Ecx) & 0x1F) - (LOBYTE(Ecx) & 0x1F);
312 if (CursorHeight < 1) CursorHeight = 1;
313 if (CursorHeight > 100) CursorHeight = 100;
314
315 /* Set the cursor */
316 CursorInfo.dwSize = (CursorHeight * 100) / CONSOLE_FONT_HEIGHT;
317 CursorInfo.bVisible = !Invisible;
318 SetConsoleCursorInfo(ConsoleOutput, &CursorInfo);
319
320 break;
321 }
322
323 /* Set Cursor Position */
324 case 0x02:
325 {
326 Position.X = LOBYTE(Edx);
327 Position.Y = HIBYTE(Edx);
328
329 SetConsoleCursorPosition(ConsoleOutput, Position);
330 break;
331 }
332
333 /* Scroll Up/Down Window */
334 case 0x06:
335 case 0x07:
336 {
337 Rect.Top = HIBYTE(Ecx);
338 Rect.Left = LOBYTE(Ecx);
339 Rect.Bottom = HIBYTE(Edx);
340 Rect.Right = LOBYTE(Edx);
341 Character.Char.UnicodeChar = L' ';
342 Character.Attributes = HIBYTE(Ebx);
343 Position.X = Rect.Left;
344 if (HIBYTE(Eax) == 0x06) Position.Y = Rect.Top - LOBYTE(Eax);
345 else Position.Y = Rect.Top + LOBYTE(Eax);
346
347 ScrollConsoleScreenBuffer(ConsoleOutput,
348 &Rect,
349 &Rect,
350 Position,
351 &Character);
352 break;
353 }
354
355 /* Read Character And Attribute At Cursor Position */
356 case 0x08:
357 {
358 break;
359 }
360
361 /* Write Character And Attribute At Cursor Position */
362 case 0x09:
363 {
364 break;
365 }
366
367 /* Write Character Only At Cursor Position */
368 case 0x0A:
369 {
370 break;
371 }
372 }
373 }
374
375 VOID BiosKeyboardService()
376 {
377 DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
378
379 switch (HIBYTE(Eax))
380 {
381 case 0x00:
382 {
383 /* Read the character (and wait if necessary) */
384 EmulatorSetRegister(EMULATOR_REG_AX, BiosGetCharacter());
385
386 break;
387 }
388
389 case 0x01:
390 {
391 WORD Data = BiosPeekCharacter();
392
393 if (Data != 0xFFFF)
394 {
395 /* There is a character, clear ZF and return it */
396 EmulatorSetRegister(EMULATOR_REG_AX, Data);
397 EmulatorClearFlag(EMULATOR_FLAG_ZF);
398 }
399 else
400 {
401 /* No character, set ZF */
402 EmulatorSetFlag(EMULATOR_FLAG_ZF);
403 }
404
405 break;
406 }
407 }
408 }
409
410 VOID BiosTimeService()
411 {
412 DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
413 DWORD Ecx = EmulatorGetRegister(EMULATOR_REG_CX);
414 DWORD Edx = EmulatorGetRegister(EMULATOR_REG_DX);
415
416 switch (HIBYTE(Eax))
417 {
418 case 0x00:
419 {
420 /* Set AL to 1 if midnight had passed, 0 otherwise */
421 Eax &= 0xFFFFFF00;
422 if (BiosPassedMidnight) Eax |= 1;
423
424 /* Return the tick count in CX:DX */
425 EmulatorSetRegister(EMULATOR_REG_AX, Eax);
426 EmulatorSetRegister(EMULATOR_REG_CX, HIWORD(BiosTickCount));
427 EmulatorSetRegister(EMULATOR_REG_DX, LOWORD(BiosTickCount));
428
429 /* Reset the midnight flag */
430 BiosPassedMidnight = FALSE;
431
432 break;
433 }
434
435 case 0x01:
436 {
437 /* Set the tick count to CX:DX */
438 BiosTickCount = MAKELONG(LOWORD(Edx), LOWORD(Ecx));
439
440 /* Reset the midnight flag */
441 BiosPassedMidnight = FALSE;
442
443 break;
444 }
445 }
446 }
447
448 VOID BiosEquipmentService()
449 {
450 /* Return the equipment list */
451 EmulatorSetRegister(EMULATOR_REG_AX, BIOS_EQUIPMENT_LIST);
452 }
453
454 VOID BiosHandleIrq(BYTE IrqNumber)
455 {
456 switch (IrqNumber)
457 {
458 /* PIT IRQ */
459 case 0:
460 {
461 /* Increase the system tick count */
462 BiosTickCount++;
463
464 /* Perform the system timer interrupt */
465 EmulatorInterrupt(0x1C);
466
467 break;
468 }
469
470 /* Keyboard IRQ */
471 case 1:
472 {
473 BYTE ScanCode, VirtualKey;
474 WORD Character;
475
476 /* Check if there is a scancode available */
477 if (!(KeyboardReadStatus() & 1)) break;
478
479 /* Get the scan code and virtual key code */
480 ScanCode = KeyboardReadData();
481 VirtualKey = MapVirtualKey(ScanCode, MAPVK_VSC_TO_VK);
482
483 /* Check if this is a key press or release */
484 if (!(ScanCode & (1 << 7)))
485 {
486 /* Key press */
487 if (VirtualKey == VK_NUMLOCK
488 || VirtualKey == VK_CAPITAL
489 || VirtualKey == VK_SCROLL)
490 {
491 /* For toggle keys, toggle the lowest bit in the keyboard map */
492 BiosKeyboardMap[VirtualKey] ^= ~(1 << 0);
493 }
494
495 /* Set the highest bit */
496 BiosKeyboardMap[VirtualKey] |= (1 << 7);
497
498 /* Find out which character this is */
499 ToAscii(ScanCode, VirtualKey, BiosKeyboardMap, &Character, 0);
500
501 /* Push it onto the BIOS keyboard queue */
502 BiosKbdBufferPush((ScanCode << 8) | (Character & 0xFF));
503 }
504 else
505 {
506 /* Key release, unset the highest bit */
507 BiosKeyboardMap[VirtualKey] &= ~(1 << 7);
508 }
509
510 break;
511 }
512 }
513
514 /* Send End-of-Interrupt to the PIC */
515 if (IrqNumber > 8) PicWriteCommand(PIC_SLAVE_CMD, PIC_OCW2_EOI);
516 PicWriteCommand(PIC_MASTER_CMD, PIC_OCW2_EOI);
517 }
518
519 /* EOF */