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