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