[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 BiosKeyboardMap[256];
20 static WORD BiosKbdBuffer[BIOS_KBD_BUFFER_SIZE];
21 static UINT BiosKbdBufferStart = 0, BiosKbdBufferEnd = 0;
22 static BOOLEAN BiosKbdBufferEmpty = TRUE;
23 static DWORD BiosTickCount = 0;
24 static BOOLEAN BiosPassedMidnight = FALSE;
25 static HANDLE BiosConsoleInput = INVALID_HANDLE_VALUE;
26 static HANDLE BiosConsoleOutput = INVALID_HANDLE_VALUE;
27 static BYTE CurrentVideoMode = BIOS_DEFAULT_VIDEO_MODE;
28 static BYTE CurrentVideoPage = 0;
29 static HANDLE ConsoleBuffers[BIOS_MAX_PAGES] = { NULL };
30 static LPVOID ConsoleFramebuffers[BIOS_MAX_PAGES] = { NULL };
31 static HANDLE ConsoleMutexes[BIOS_MAX_PAGES] = { NULL };
32 static BOOLEAN VideoNeedsUpdate = TRUE;
33 static SMALL_RECT UpdateRectangle = { 0, 0, 0, 0 };
34 static CONSOLE_SCREEN_BUFFER_INFO BiosSavedBufferInfo;
35 static VIDEO_MODE VideoModes[] =
36 {
37 /* Width | Height | Text | Colors | Gray | Pages | Segment */
38 { 40, 25, TRUE, 16, TRUE, 8, 0xB800}, /* Mode 00h */
39 { 40, 25, TRUE, 16, FALSE, 8, 0xB800}, /* Mode 01h */
40 { 80, 25, TRUE, 16, TRUE, 8, 0xB800}, /* Mode 02h */
41 { 80, 25, TRUE, 16, FALSE, 8, 0xB800}, /* Mode 03h */
42 { 320, 200, FALSE, 4, FALSE, 4, 0xB800}, /* Mode 04h */
43 { 320, 200, FALSE, 4, TRUE, 4, 0xB800}, /* Mode 05h */
44 { 640, 200, FALSE, 2, FALSE, 2, 0xB800}, /* Mode 06h */
45 { 80, 25, TRUE, 3, FALSE, 1, 0xB000}, /* Mode 07h */
46 { 0, 0, FALSE, 0, FALSE, 0, 0x0000}, /* Mode 08h - not used */
47 { 0, 0, FALSE, 0, FALSE, 0, 0x0000}, /* Mode 09h - not used */
48 { 0, 0, FALSE, 0, FALSE, 0, 0x0000}, /* Mode 0Ah - not used */
49 { 0, 0, FALSE, 0, FALSE, 0, 0x0000}, /* Mode 0Bh - not used */
50 { 0, 0, FALSE, 0, FALSE, 0, 0x0000}, /* Mode 0Ch - not used */
51 { 320, 200, FALSE, 16, FALSE, 8, 0xA000}, /* Mode 0Dh */
52 { 640, 200, FALSE, 16, FALSE, 4, 0xA000}, /* Mode 0Eh */
53 { 640, 350, FALSE, 3, FALSE, 2, 0xA000}, /* Mode 0Fh */
54 { 640, 350, FALSE, 4, FALSE, 2, 0xA000}, /* Mode 10h */
55 { 640, 480, FALSE, 2, FALSE, 1, 0xA000}, /* Mode 11h */
56 { 640, 480, FALSE, 16, FALSE, 1, 0xA000}, /* Mode 12h */
57 { 640, 480, FALSE, 256, FALSE, 1, 0xA000} /* Mode 13h */
58 };
59
60 /* PRIVATE FUNCTIONS **********************************************************/
61
62 static INT BiosColorNumberToBits(DWORD Colors)
63 {
64 INT i;
65
66 /* Find the index of the highest-order bit */
67 for (i = 31; i >= 0; i--) if (Colors & (1 << i)) break;
68
69 /* Special case for zero */
70 if (i == 0) i = 32;
71
72 return i;
73 }
74
75 static DWORD BiosGetVideoPageSize()
76 {
77 INT i;
78 DWORD BufferSize = VideoModes[CurrentVideoMode].Width
79 * VideoModes[CurrentVideoMode].Height
80 * BiosColorNumberToBits(VideoModes[CurrentVideoMode].Colors)
81 / 8;
82
83 for (i = 0; i < 32; i++) if ((1 << i) >= BufferSize) break;
84
85 return 1 << i;
86 }
87
88 static BYTE BiosVideoAddressToPage(ULONG Address)
89 {
90 return (Address - (VideoModes[CurrentVideoMode].Segment << 4))
91 / BiosGetVideoPageSize();
92 }
93
94 static COORD BiosVideoAddressToCoord(ULONG Address)
95 {
96 COORD Result = {0, 0};
97 INT BitsPerPixel;
98 BYTE PageStart = BiosVideoAddressToPage(Address) * BiosGetVideoPageSize();
99 DWORD Offset = Address - (VideoModes[CurrentVideoMode].Segment << 4) - PageStart;
100
101 if (VideoModes[CurrentVideoMode].Text)
102 {
103 Result.X = (Offset / sizeof(WORD)) % VideoModes[CurrentVideoMode].Width;
104 Result.Y = (Offset / sizeof(WORD)) / VideoModes[CurrentVideoMode].Width;
105 }
106 else
107 {
108 BitsPerPixel = BiosColorNumberToBits(VideoModes[CurrentVideoMode].Colors);
109
110 Result.X = ((Offset * 8) / BitsPerPixel)
111 % VideoModes[CurrentVideoMode].Width;
112 Result.Y = ((Offset * 8) / BitsPerPixel)
113 / VideoModes[CurrentVideoMode].Width;
114 }
115
116 return Result;
117 }
118
119 static BOOLEAN BiosKbdBufferPush(WORD Data)
120 {
121 /* If it's full, fail */
122 if (!BiosKbdBufferEmpty && (BiosKbdBufferStart == BiosKbdBufferEnd))
123 {
124 return FALSE;
125 }
126
127 /* Otherwise, add the value to the queue */
128 BiosKbdBuffer[BiosKbdBufferEnd] = Data;
129 BiosKbdBufferEnd++;
130 BiosKbdBufferEnd %= BIOS_KBD_BUFFER_SIZE;
131 BiosKbdBufferEmpty = FALSE;
132
133 /* Return success */
134 return TRUE;
135 }
136
137 static BOOLEAN BiosKbdBufferTop(LPWORD Data)
138 {
139 /* If it's empty, fail */
140 if (BiosKbdBufferEmpty) return FALSE;
141
142 /* Otherwise, get the value and return success */
143 *Data = BiosKbdBuffer[BiosKbdBufferStart];
144 return TRUE;
145 }
146
147 static BOOLEAN BiosKbdBufferPop()
148 {
149 /* If it's empty, fail */
150 if (BiosKbdBufferEmpty) return FALSE;
151
152 /* Otherwise, remove the value and return success */
153 BiosKbdBufferStart++;
154 BiosKbdBufferStart %= BIOS_KBD_BUFFER_SIZE;
155 if (BiosKbdBufferStart == BiosKbdBufferEnd) BiosKbdBufferEmpty = TRUE;
156
157 return TRUE;
158 }
159
160 /* PUBLIC FUNCTIONS ***********************************************************/
161
162 BYTE BiosGetVideoMode()
163 {
164 return CurrentVideoMode;
165 }
166
167 BOOLEAN BiosSetVideoMode(BYTE ModeNumber)
168 {
169 INT i;
170 COORD Coord;
171 CONSOLE_GRAPHICS_BUFFER_INFO GraphicsBufferInfo;
172 LPBITMAPINFO BitmapInfo;
173 LPWORD PaletteIndex;
174
175 /* Make sure this is a valid video mode */
176 if (ModeNumber > BIOS_MAX_VIDEO_MODE) return FALSE;
177 if (VideoModes[ModeNumber].Pages == 0) return FALSE;
178
179 /* Free the current buffers */
180 for (i = 0; i < VideoModes[CurrentVideoMode].Pages; i++)
181 {
182 if (ConsoleBuffers[i] != NULL) CloseHandle(ConsoleBuffers[i]);
183 if (!VideoModes[CurrentVideoMode].Text) CloseHandle(ConsoleMutexes[i]);
184 }
185
186 if (VideoModes[ModeNumber].Text)
187 {
188 /* Page 0 is CONOUT$ */
189 ConsoleBuffers[0] = CreateFile(TEXT("CONOUT$"),
190 GENERIC_READ | GENERIC_WRITE,
191 FILE_SHARE_READ | FILE_SHARE_WRITE,
192 NULL,
193 OPEN_EXISTING,
194 0,
195 NULL);
196
197 /* Set the current page to page 0 */
198 CurrentVideoPage = 0;
199
200 /* Create console buffers for other pages */
201 for (i = 1; i < VideoModes[ModeNumber].Pages; i++)
202 {
203 ConsoleBuffers[i] = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
204 FILE_SHARE_READ | FILE_SHARE_WRITE,
205 NULL,
206 CONSOLE_TEXTMODE_BUFFER,
207 NULL);
208 }
209
210 /* Set the size for the buffers */
211 for (i = 0; i < VideoModes[ModeNumber].Pages; i++)
212 {
213 Coord.X = VideoModes[ModeNumber].Width;
214 Coord.Y = VideoModes[ModeNumber].Height;
215
216 SetConsoleScreenBufferSize(ConsoleBuffers[i], Coord);
217 }
218 }
219 else
220 {
221 /* Allocate a bitmap info structure */
222 BitmapInfo = (LPBITMAPINFO)HeapAlloc(GetProcessHeap(),
223 HEAP_ZERO_MEMORY,
224 sizeof(BITMAPINFOHEADER)
225 + VideoModes[ModeNumber].Colors
226 * sizeof(WORD));
227 if (BitmapInfo == NULL) return FALSE;
228
229 /* Fill the bitmap info header */
230 ZeroMemory(&BitmapInfo->bmiHeader, sizeof(BITMAPINFOHEADER));
231 BitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
232 BitmapInfo->bmiHeader.biWidth = VideoModes[ModeNumber].Width;
233 BitmapInfo->bmiHeader.biHeight = VideoModes[ModeNumber].Height;
234 BitmapInfo->bmiHeader.biPlanes = 1;
235 BitmapInfo->bmiHeader.biCompression = BI_RGB;
236 BitmapInfo->bmiHeader.biBitCount = BiosColorNumberToBits(VideoModes[ModeNumber].Colors);
237
238 /* Calculate the image size */
239 BitmapInfo->bmiHeader.biSizeImage = BitmapInfo->bmiHeader.biWidth
240 * BitmapInfo->bmiHeader.biHeight
241 * (BitmapInfo->bmiHeader.biBitCount >> 3);
242
243 /* Fill the palette data */
244 PaletteIndex = (LPWORD)((ULONG_PTR)BitmapInfo + sizeof(BITMAPINFOHEADER));
245 for (i = 0; i < VideoModes[ModeNumber].Colors; i++)
246 {
247 PaletteIndex[i] = i;
248 }
249
250 /* Create a console buffer for each page */
251 for (i = 0; i < VideoModes[ModeNumber].Pages; i++)
252 {
253 /* Fill the console graphics buffer info */
254 GraphicsBufferInfo.dwBitMapInfoLength = sizeof(BITMAPINFOHEADER)
255 + VideoModes[ModeNumber].Colors
256 * sizeof(WORD);
257 GraphicsBufferInfo.lpBitMapInfo = BitmapInfo;
258 GraphicsBufferInfo.dwUsage = DIB_PAL_COLORS;
259
260 /* Create the buffer */
261 ConsoleBuffers[i] = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
262 FILE_SHARE_READ | FILE_SHARE_WRITE,
263 NULL,
264 CONSOLE_GRAPHICS_BUFFER,
265 &GraphicsBufferInfo);
266
267 /* Save the framebuffer address and mutex */
268 ConsoleFramebuffers[i] = GraphicsBufferInfo.lpBitMap;
269 ConsoleMutexes[i] = GraphicsBufferInfo.hMutex;
270 }
271
272 /* Free the bitmap information */
273 HeapFree(GetProcessHeap(), 0, BitmapInfo);
274 }
275
276 /* Set the active page console buffer */
277 SetConsoleActiveScreenBuffer(ConsoleBuffers[CurrentVideoPage]);
278
279 /* Update the mode number */
280 CurrentVideoMode = ModeNumber;
281
282 return TRUE;
283 }
284
285 inline DWORD BiosGetVideoMemoryStart()
286 {
287 return (VideoModes[CurrentVideoMode].Segment << 4);
288 }
289
290 inline VOID BiosVerticalRefresh()
291 {
292 /* Ignore if we're in text mode */
293 if (VideoModes[CurrentVideoMode].Text) return;
294
295 /* Ignore if there's nothing to update */
296 if (!VideoNeedsUpdate) return;
297
298 /* Redraw the screen */
299 InvalidateConsoleDIBits(ConsoleBuffers[CurrentVideoPage],
300 &UpdateRectangle);
301
302 /* Clear the update flag */
303 VideoNeedsUpdate = FALSE;
304 }
305
306 BOOLEAN BiosInitialize()
307 {
308 INT i;
309 WORD Offset = 0;
310 LPWORD IntVecTable = (LPWORD)((ULONG_PTR)BaseAddress);
311 LPBYTE BiosCode = (LPBYTE)((ULONG_PTR)BaseAddress + TO_LINEAR(BIOS_SEGMENT, 0));
312
313 /* Generate ISR stubs and fill the IVT */
314 for (i = 0; i < 256; i++)
315 {
316 IntVecTable[i * 2] = Offset;
317 IntVecTable[i * 2 + 1] = BIOS_SEGMENT;
318
319 if (i != SPECIAL_INT_NUM)
320 {
321 BiosCode[Offset++] = 0xFA; // cli
322
323 BiosCode[Offset++] = 0x6A; // push i
324 BiosCode[Offset++] = (BYTE)i;
325
326 BiosCode[Offset++] = 0xCD; // int SPECIAL_INT_NUM
327 BiosCode[Offset++] = SPECIAL_INT_NUM;
328
329 BiosCode[Offset++] = 0x83; // add sp, 2
330 BiosCode[Offset++] = 0xC4;
331 BiosCode[Offset++] = 0x02;
332 }
333
334 BiosCode[Offset++] = 0xCF; // iret
335 }
336
337 /* Get the input and output handles to the real console */
338 BiosConsoleInput = CreateFile(TEXT("CONIN$"),
339 GENERIC_READ | GENERIC_WRITE,
340 FILE_SHARE_READ | FILE_SHARE_WRITE,
341 NULL,
342 OPEN_EXISTING,
343 0,
344 NULL);
345
346 BiosConsoleOutput = CreateFile(TEXT("CONOUT$"),
347 GENERIC_READ | GENERIC_WRITE,
348 FILE_SHARE_READ | FILE_SHARE_WRITE,
349 NULL,
350 OPEN_EXISTING,
351 0,
352 NULL);
353
354 /* Make sure it was successful */
355 if ((BiosConsoleInput == INVALID_HANDLE_VALUE)
356 || (BiosConsoleOutput == INVALID_HANDLE_VALUE))
357 {
358 return FALSE;
359 }
360
361 /* Save the console screen buffer information */
362 if (!GetConsoleScreenBufferInfo(BiosConsoleOutput, &BiosSavedBufferInfo))
363 {
364 return FALSE;
365 }
366
367 /* Set the default video mode */
368 BiosSetVideoMode(BIOS_DEFAULT_VIDEO_MODE);
369
370 /* Set the console input mode */
371 SetConsoleMode(BiosConsoleInput, ENABLE_MOUSE_INPUT | ENABLE_PROCESSED_INPUT);
372
373 /* Initialize the PIC */
374 PicWriteCommand(PIC_MASTER_CMD, PIC_ICW1 | PIC_ICW1_ICW4);
375 PicWriteCommand(PIC_SLAVE_CMD, PIC_ICW1 | PIC_ICW1_ICW4);
376
377 /* Set the interrupt offsets */
378 PicWriteData(PIC_MASTER_DATA, BIOS_PIC_MASTER_INT);
379 PicWriteData(PIC_SLAVE_DATA, BIOS_PIC_SLAVE_INT);
380
381 /* Tell the master PIC there is a slave at IRQ 2 */
382 PicWriteData(PIC_MASTER_DATA, 1 << 2);
383 PicWriteData(PIC_SLAVE_DATA, 2);
384
385 /* Make sure the PIC is in 8086 mode */
386 PicWriteData(PIC_MASTER_DATA, PIC_ICW4_8086);
387 PicWriteData(PIC_SLAVE_DATA, PIC_ICW4_8086);
388
389 /* Clear the masks for both PICs */
390 PicWriteData(PIC_MASTER_DATA, 0x00);
391 PicWriteData(PIC_SLAVE_DATA, 0x00);
392
393 PitWriteCommand(0x34);
394 PitWriteData(0, 0x00);
395 PitWriteData(0, 0x00);
396
397 return TRUE;
398 }
399
400 VOID BiosCleanup()
401 {
402 INT i;
403
404 /* Restore the old screen buffer */
405 SetConsoleActiveScreenBuffer(BiosConsoleOutput);
406
407 /* Restore the screen buffer size */
408 SetConsoleScreenBufferSize(BiosConsoleOutput, BiosSavedBufferInfo.dwSize);
409
410 /* Free the buffers */
411 for (i = 0; i < VideoModes[CurrentVideoMode].Pages; i++)
412 {
413 if (ConsoleBuffers[i] != NULL) CloseHandle(ConsoleBuffers[i]);
414 if (!VideoModes[CurrentVideoMode].Text) CloseHandle(ConsoleMutexes[i]);
415 }
416
417 /* Close the console handles */
418 if (BiosConsoleInput != INVALID_HANDLE_VALUE) CloseHandle(BiosConsoleInput);
419 if (BiosConsoleOutput != INVALID_HANDLE_VALUE) CloseHandle(BiosConsoleOutput);
420 }
421
422 VOID BiosUpdateConsole(ULONG StartAddress, ULONG EndAddress)
423 {
424 ULONG i;
425 COORD Coordinates;
426 BYTE Page;
427 COORD Origin = { 0, 0 };
428 COORD UnitSize = { 1, 1 };
429 CHAR_INFO Character;
430 SMALL_RECT Rect;
431
432 /* Start from the character address */
433 StartAddress &= ~1;
434
435 if (VideoModes[CurrentVideoMode].Text)
436 {
437 /* Loop through all the addresses */
438 for (i = StartAddress; i < EndAddress; i += 2)
439 {
440 /* Get the coordinates */
441 Coordinates = BiosVideoAddressToCoord(i);
442
443 /* Get the page number */
444 Page = BiosVideoAddressToPage(i);
445
446 /* Make sure the page is valid */
447 if (Page >= VideoModes[CurrentVideoMode].Pages) continue;
448
449 /* Fill the rectangle structure */
450 Rect.Left = Coordinates.X;
451 Rect.Top = Coordinates.Y;
452 Rect.Right = Rect.Left;
453 Rect.Bottom = Rect.Top;
454
455 /* Fill the character data */
456 Character.Char.AsciiChar = *((PCHAR)((ULONG_PTR)BaseAddress + i));
457 Character.Attributes = *((PBYTE)((ULONG_PTR)BaseAddress + i + 1));
458
459 /* Write the character */
460 WriteConsoleOutputA(ConsoleBuffers[Page],
461 &Character,
462 UnitSize,
463 Origin,
464 &Rect);
465 }
466 }
467 else
468 {
469 /* Wait for the mutex object */
470 WaitForSingleObject(ConsoleMutexes[CurrentVideoPage], INFINITE);
471
472 /* Copy the data to the framebuffer */
473 RtlCopyMemory((LPVOID)((ULONG_PTR)ConsoleFramebuffers[CurrentVideoPage]
474 + StartAddress - BiosGetVideoMemoryStart()),
475 (LPVOID)((ULONG_PTR)BaseAddress + StartAddress),
476 EndAddress - StartAddress);
477
478 /* Release the mutex */
479 ReleaseMutex(ConsoleMutexes[CurrentVideoPage]);
480
481 /* Check if this is the first time the rectangle is updated */
482 if (!VideoNeedsUpdate)
483 {
484 UpdateRectangle.Left = UpdateRectangle.Top = (SHORT)0x7FFF;
485 UpdateRectangle.Right = UpdateRectangle.Bottom = (SHORT)0x8000;
486 }
487
488 /* Expand the update rectangle */
489 for (i = StartAddress; i < EndAddress; i++)
490 {
491 /* Get the coordinates */
492 Coordinates = BiosVideoAddressToCoord(i);
493
494 /* Expand the rectangle to include the point */
495 UpdateRectangle.Left = min(UpdateRectangle.Left, Coordinates.X);
496 UpdateRectangle.Right = max(UpdateRectangle.Right, Coordinates.X);
497 UpdateRectangle.Top = min(UpdateRectangle.Top, Coordinates.Y);
498 UpdateRectangle.Bottom = max(UpdateRectangle.Bottom, Coordinates.Y);
499 }
500
501 /* Set the update flag */
502 VideoNeedsUpdate = TRUE;
503 }
504 }
505
506 VOID BiosUpdateVideoMemory(ULONG StartAddress, ULONG EndAddress)
507 {
508 ULONG i;
509 COORD Coordinates;
510 BYTE Page;
511 WORD Attribute;
512 DWORD CharsWritten;
513
514 if (VideoModes[CurrentVideoMode].Text)
515 {
516 /* Loop through all the addresses */
517 for (i = StartAddress; i < EndAddress; i++)
518 {
519 /* Get the coordinates */
520 Coordinates = BiosVideoAddressToCoord(i);
521
522 /* Get the page number */
523 Page = BiosVideoAddressToPage(i);
524
525 /* Make sure the page is valid */
526 if (Page >= VideoModes[CurrentVideoMode].Pages) continue;
527
528 /* Check if this is a character byte or an attribute byte */
529 if ((i - (VideoModes[CurrentVideoMode].Segment << 4)) % 2 == 0)
530 {
531 /* This is a regular character */
532 ReadConsoleOutputCharacterA(ConsoleBuffers[Page],
533 (LPSTR)((ULONG_PTR)BaseAddress + i),
534 sizeof(CHAR),
535 Coordinates,
536 &CharsWritten);
537 }
538 else
539 {
540 /* This is an attribute */
541 ReadConsoleOutputAttribute(ConsoleBuffers[Page],
542 &Attribute,
543 sizeof(CHAR),
544 Coordinates,
545 &CharsWritten);
546
547 *(PCHAR)((ULONG_PTR)BaseAddress + i) = LOBYTE(Attribute);
548 }
549 }
550 }
551 else
552 {
553 /* Wait for the mutex object */
554 WaitForSingleObject(ConsoleMutexes[CurrentVideoPage], INFINITE);
555
556 /* Copy the data to the emulator memory */
557 RtlCopyMemory((LPVOID)((ULONG_PTR)BaseAddress + StartAddress),
558 (LPVOID)((ULONG_PTR)ConsoleFramebuffers[CurrentVideoPage]
559 + StartAddress - BiosGetVideoMemoryStart()),
560 EndAddress - StartAddress);
561
562 /* Release the mutex */
563 ReleaseMutex(ConsoleMutexes[CurrentVideoPage]);
564 }
565 }
566
567 WORD BiosPeekCharacter()
568 {
569 WORD CharacterData;
570
571 /* Check if there is a key available */
572 if (BiosKbdBufferEmpty) return 0xFFFF;
573
574 /* Get the key from the queue, but don't remove it */
575 BiosKbdBufferTop(&CharacterData);
576
577 return CharacterData;
578 }
579
580 WORD BiosGetCharacter()
581 {
582 WORD CharacterData;
583 INPUT_RECORD InputRecord;
584 DWORD Count;
585
586 /* Check if there is a key available */
587 if (!BiosKbdBufferEmpty)
588 {
589 /* Get the key from the queue, and remove it */
590 BiosKbdBufferTop(&CharacterData);
591 BiosKbdBufferPop();
592 }
593 else
594 {
595 while (TRUE)
596 {
597 /* Wait for a console event */
598 WaitForSingleObject(BiosConsoleInput, INFINITE);
599
600 /* Read the event, and make sure it's a keypress */
601 if (!ReadConsoleInput(BiosConsoleInput, &InputRecord, 1, &Count)) continue;
602 if (InputRecord.EventType != KEY_EVENT) continue;
603 if (!InputRecord.Event.KeyEvent.bKeyDown) continue;
604
605 /* Save the scan code and end the loop */
606 CharacterData = (InputRecord.Event.KeyEvent.wVirtualScanCode << 8)
607 | InputRecord.Event.KeyEvent.uChar.AsciiChar;
608
609 break;
610 }
611 }
612
613 return CharacterData;
614 }
615
616 VOID BiosVideoService()
617 {
618 INT CursorHeight;
619 BOOLEAN Invisible = FALSE;
620 COORD Position;
621 CONSOLE_CURSOR_INFO CursorInfo;
622 CONSOLE_SCREEN_BUFFER_INFO ScreenBufferInfo;
623 CHAR_INFO Character;
624 SMALL_RECT Rect;
625 DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
626 DWORD Ecx = EmulatorGetRegister(EMULATOR_REG_CX);
627 DWORD Edx = EmulatorGetRegister(EMULATOR_REG_DX);
628 DWORD Ebx = EmulatorGetRegister(EMULATOR_REG_BX);
629
630 switch (HIBYTE(Eax))
631 {
632 /* Set Video Mode */
633 case 0x00:
634 {
635 BiosSetVideoMode(LOBYTE(Eax));
636 break;
637 }
638
639 /* Set Text-Mode Cursor Shape */
640 case 0x01:
641 {
642 /* Retrieve and validate the input */
643 Invisible = ((HIBYTE(Ecx) >> 5) & 0x03) ? TRUE : FALSE;
644 CursorHeight = (HIBYTE(Ecx) & 0x1F) - (LOBYTE(Ecx) & 0x1F);
645 if (CursorHeight < 1) CursorHeight = 1;
646 if (CursorHeight > 100) CursorHeight = 100;
647
648 /* Set the cursor */
649 CursorInfo.dwSize = (CursorHeight * 100) / CONSOLE_FONT_HEIGHT;
650 CursorInfo.bVisible = !Invisible;
651 SetConsoleCursorInfo(ConsoleBuffers[CurrentVideoPage], &CursorInfo);
652
653 break;
654 }
655
656 /* Set Cursor Position */
657 case 0x02:
658 {
659 /* Make sure the selected video page exists */
660 if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
661
662 Position.X = LOBYTE(Edx);
663 Position.Y = HIBYTE(Edx);
664
665 SetConsoleCursorPosition(ConsoleBuffers[HIBYTE(Ebx)], Position);
666 break;
667 }
668
669 /* Get Cursor Position */
670 case 0x03:
671 {
672 INT StartLine;
673
674 /* Make sure the selected video page exists */
675 if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
676
677 /* Retrieve the data */
678 GetConsoleCursorInfo(ConsoleBuffers[HIBYTE(Ebx)], &CursorInfo);
679 GetConsoleScreenBufferInfo(ConsoleBuffers[HIBYTE(Ebx)],
680 &ScreenBufferInfo);
681
682 /* Find the first line */
683 StartLine = 32 - ((CursorInfo.dwSize * 32) / 100);
684
685 /* Return the result */
686 EmulatorSetRegister(EMULATOR_REG_AX, 0);
687 EmulatorSetRegister(EMULATOR_REG_CX, (StartLine << 8) | 0x1F);
688 EmulatorSetRegister(EMULATOR_REG_DX,
689 LOWORD(ScreenBufferInfo.dwCursorPosition.Y) << 8
690 || LOWORD(ScreenBufferInfo.dwCursorPosition.X));
691 break;
692 }
693
694 /* Select Active Display Page */
695 case 0x05:
696 {
697 /* Check if the page exists */
698 if (LOBYTE(Eax) >= VideoModes[CurrentVideoMode].Pages) break;
699
700 /* Check if this is the same page */
701 if (LOBYTE(Eax) == CurrentVideoPage) break;
702
703 /* Change the video page */
704 CurrentVideoPage = LOBYTE(Eax);
705
706 /* Set the active page console buffer */
707 SetConsoleActiveScreenBuffer(ConsoleBuffers[CurrentVideoPage]);
708
709 /* Restore the cursor to (0, 0) */
710 Position.X = Position.Y = 0;
711 SetConsoleCursorPosition(BiosConsoleOutput, Position);
712
713 break;
714 }
715
716 /* Scroll Up/Down Window */
717 case 0x06:
718 case 0x07:
719 {
720 Rect.Top = HIBYTE(Ecx);
721 Rect.Left = LOBYTE(Ecx);
722 Rect.Bottom = HIBYTE(Edx);
723 Rect.Right = LOBYTE(Edx);
724 Character.Char.UnicodeChar = L' ';
725 Character.Attributes = HIBYTE(Ebx);
726 Position.X = Rect.Left;
727 if (HIBYTE(Eax) == 0x06) Position.Y = Rect.Top - LOBYTE(Eax);
728 else Position.Y = Rect.Top + LOBYTE(Eax);
729
730 ScrollConsoleScreenBuffer(ConsoleBuffers[CurrentVideoPage],
731 &Rect,
732 &Rect,
733 Position,
734 &Character);
735 break;
736 }
737
738 /* Read Character And Attribute At Cursor Position */
739 case 0x08:
740 {
741 COORD BufferSize = { 1, 1 }, Origin = { 0, 0 };
742
743 /* Make sure the selected video page exists */
744 if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
745
746 /* Get the cursor position */
747 GetConsoleScreenBufferInfo(ConsoleBuffers[HIBYTE(Ebx)],
748 &ScreenBufferInfo);
749
750 /* Read at cursor position */
751 Rect.Left = ScreenBufferInfo.dwCursorPosition.X;
752 Rect.Top = ScreenBufferInfo.dwCursorPosition.Y;
753
754 /* Read the console output */
755 ReadConsoleOutput(ConsoleBuffers[HIBYTE(Ebx)],
756 &Character,
757 BufferSize,
758 Origin,
759 &Rect);
760
761 /* Return the result */
762 EmulatorSetRegister(EMULATOR_REG_AX,
763 (LOBYTE(Character.Attributes) << 8)
764 | Character.Char.AsciiChar);
765
766 break;
767 }
768
769 /* Write Character And Attribute At Cursor Position */
770 case 0x09:
771 {
772 DWORD CharsWritten;
773
774 /* Make sure the selected video page exists */
775 if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
776
777 /* Get the cursor position */
778 GetConsoleScreenBufferInfo(ConsoleBuffers[HIBYTE(Ebx)],
779 &ScreenBufferInfo);
780
781 /* Write the attribute to the output */
782 FillConsoleOutputAttribute(ConsoleBuffers[HIBYTE(Ebx)],
783 LOBYTE(Ebx),
784 LOWORD(Ecx),
785 ScreenBufferInfo.dwCursorPosition,
786 &CharsWritten);
787
788 /* Write the character to the output */
789 FillConsoleOutputCharacterA(ConsoleBuffers[HIBYTE(Ebx)],
790 LOBYTE(Eax),
791 LOWORD(Ecx),
792 ScreenBufferInfo.dwCursorPosition,
793 &CharsWritten);
794
795 break;
796 }
797
798 /* Write Character Only At Cursor Position */
799 case 0x0A:
800 {
801 DWORD CharsWritten;
802
803 /* Make sure the selected video page exists */
804 if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
805
806 /* Get the cursor position */
807 GetConsoleScreenBufferInfo(ConsoleBuffers[HIBYTE(Ebx)],
808 &ScreenBufferInfo);
809
810 /* Write the character to the output */
811 FillConsoleOutputCharacterA(ConsoleBuffers[HIBYTE(Ebx)],
812 LOBYTE(Eax),
813 LOWORD(Ecx),
814 ScreenBufferInfo.dwCursorPosition,
815 &CharsWritten);
816
817 break;
818 }
819
820 /* Teletype Output */
821 case 0x0E:
822 {
823 CHAR Character = LOBYTE(Eax);
824 DWORD NumWritten;
825
826 /* Make sure the page exists */
827 if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
828
829 /* Set the attribute */
830 SetConsoleTextAttribute(ConsoleBuffers[HIBYTE(Ebx)], LOBYTE(Ebx));
831
832 /* Write the character */
833 WriteConsoleA(ConsoleBuffers[HIBYTE(Ebx)],
834 &Character,
835 sizeof(CHAR),
836 &NumWritten,
837 NULL);
838
839 break;
840 }
841
842 /* Get Current Video Mode */
843 case 0x0F:
844 {
845 EmulatorSetRegister(EMULATOR_REG_AX,
846 (VideoModes[CurrentVideoMode].Width << 8)
847 | CurrentVideoMode);
848 EmulatorSetRegister(EMULATOR_REG_BX,
849 (CurrentVideoPage << 8) | LOBYTE(Ebx));
850
851 break;
852 }
853
854 default:
855 {
856 DPRINT1("BIOS Function INT 10h, AH = 0x%02X NOT IMPLEMENTED\n",
857 HIBYTE(Eax));
858 }
859 }
860 }
861
862 VOID BiosKeyboardService()
863 {
864 DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
865
866 switch (HIBYTE(Eax))
867 {
868 case 0x00:
869 {
870 /* Read the character (and wait if necessary) */
871 EmulatorSetRegister(EMULATOR_REG_AX, BiosGetCharacter());
872
873 break;
874 }
875
876 case 0x01:
877 {
878 WORD Data = BiosPeekCharacter();
879
880 if (Data != 0xFFFF)
881 {
882 /* There is a character, clear ZF and return it */
883 EmulatorSetRegister(EMULATOR_REG_AX, Data);
884 EmulatorClearFlag(EMULATOR_FLAG_ZF);
885 }
886 else
887 {
888 /* No character, set ZF */
889 EmulatorSetFlag(EMULATOR_FLAG_ZF);
890 }
891
892 break;
893 }
894
895 default:
896 {
897 DPRINT1("BIOS Function INT 16h, AH = 0x%02X NOT IMPLEMENTED\n",
898 HIBYTE(Eax));
899 }
900 }
901 }
902
903 VOID BiosTimeService()
904 {
905 DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
906 DWORD Ecx = EmulatorGetRegister(EMULATOR_REG_CX);
907 DWORD Edx = EmulatorGetRegister(EMULATOR_REG_DX);
908
909 switch (HIBYTE(Eax))
910 {
911 case 0x00:
912 {
913 /* Set AL to 1 if midnight had passed, 0 otherwise */
914 Eax &= 0xFFFFFF00;
915 if (BiosPassedMidnight) Eax |= 1;
916
917 /* Return the tick count in CX:DX */
918 EmulatorSetRegister(EMULATOR_REG_AX, Eax);
919 EmulatorSetRegister(EMULATOR_REG_CX, HIWORD(BiosTickCount));
920 EmulatorSetRegister(EMULATOR_REG_DX, LOWORD(BiosTickCount));
921
922 /* Reset the midnight flag */
923 BiosPassedMidnight = FALSE;
924
925 break;
926 }
927
928 case 0x01:
929 {
930 /* Set the tick count to CX:DX */
931 BiosTickCount = MAKELONG(LOWORD(Edx), LOWORD(Ecx));
932
933 /* Reset the midnight flag */
934 BiosPassedMidnight = FALSE;
935
936 break;
937 }
938
939 default:
940 {
941 DPRINT1("BIOS Function INT 1Ah, AH = 0x%02X NOT IMPLEMENTED\n",
942 HIBYTE(Eax));
943 }
944 }
945 }
946
947 VOID BiosSystemTimerInterrupt()
948 {
949 /* Increase the system tick count */
950 BiosTickCount++;
951 }
952
953 VOID BiosEquipmentService()
954 {
955 /* Return the equipment list */
956 EmulatorSetRegister(EMULATOR_REG_AX, BIOS_EQUIPMENT_LIST);
957 }
958
959 VOID BiosHandleIrq(BYTE IrqNumber)
960 {
961 switch (IrqNumber)
962 {
963 /* PIT IRQ */
964 case 0:
965 {
966 /* Perform the system timer interrupt */
967 EmulatorInterrupt(BIOS_SYS_TIMER_INTERRUPT);
968
969 break;
970 }
971
972 /* Keyboard IRQ */
973 case 1:
974 {
975 BYTE ScanCode, VirtualKey;
976 WORD Character;
977
978 /* Check if there is a scancode available */
979 if (!(KeyboardReadStatus() & 1)) break;
980
981 /* Get the scan code and virtual key code */
982 ScanCode = KeyboardReadData();
983 VirtualKey = MapVirtualKey(ScanCode, MAPVK_VSC_TO_VK);
984
985 /* Check if this is a key press or release */
986 if (!(ScanCode & (1 << 7)))
987 {
988 /* Key press */
989 if (VirtualKey == VK_NUMLOCK
990 || VirtualKey == VK_CAPITAL
991 || VirtualKey == VK_SCROLL)
992 {
993 /* For toggle keys, toggle the lowest bit in the keyboard map */
994 BiosKeyboardMap[VirtualKey] ^= ~(1 << 0);
995 }
996
997 /* Set the highest bit */
998 BiosKeyboardMap[VirtualKey] |= (1 << 7);
999
1000 /* Find out which character this is */
1001 ToAscii(ScanCode, VirtualKey, BiosKeyboardMap, &Character, 0);
1002
1003 /* Push it onto the BIOS keyboard queue */
1004 BiosKbdBufferPush((ScanCode << 8) | (Character & 0xFF));
1005 }
1006 else
1007 {
1008 /* Key release, unset the highest bit */
1009 BiosKeyboardMap[VirtualKey] &= ~(1 << 7);
1010 }
1011
1012 break;
1013 }
1014 }
1015
1016 /* Send End-of-Interrupt to the PIC */
1017 if (IrqNumber > 8) PicWriteCommand(PIC_SLAVE_CMD, PIC_OCW2_EOI);
1018 PicWriteCommand(PIC_MASTER_CMD, PIC_OCW2_EOI);
1019 }
1020
1021 /* EOF */