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