[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 if (i != SPECIAL_INT_NUM)
378 {
379 BiosCode[Offset++] = 0xFA; // cli
380
381 BiosCode[Offset++] = 0x6A; // push i
382 BiosCode[Offset++] = (BYTE)i;
383
384 BiosCode[Offset++] = 0xCD; // int SPECIAL_INT_NUM
385 BiosCode[Offset++] = SPECIAL_INT_NUM;
386
387 BiosCode[Offset++] = 0x83; // add sp, 2
388 BiosCode[Offset++] = 0xC4;
389 BiosCode[Offset++] = 0x02;
390 }
391
392 BiosCode[Offset++] = 0xCF; // iret
393 }
394
395 /* Get the input and output handles to the real console */
396 BiosConsoleInput = CreateFile(TEXT("CONIN$"),
397 GENERIC_READ | GENERIC_WRITE,
398 FILE_SHARE_READ | FILE_SHARE_WRITE,
399 NULL,
400 OPEN_EXISTING,
401 0,
402 NULL);
403
404 BiosConsoleOutput = CreateFile(TEXT("CONOUT$"),
405 GENERIC_READ | GENERIC_WRITE,
406 FILE_SHARE_READ | FILE_SHARE_WRITE,
407 NULL,
408 OPEN_EXISTING,
409 0,
410 NULL);
411
412 /* Make sure it was successful */
413 if ((BiosConsoleInput == INVALID_HANDLE_VALUE)
414 || (BiosConsoleOutput == INVALID_HANDLE_VALUE))
415 {
416 return FALSE;
417 }
418
419 /* Save the console screen buffer information */
420 if (!GetConsoleScreenBufferInfo(BiosConsoleOutput, &BiosSavedBufferInfo))
421 {
422 return FALSE;
423 }
424
425 /* Store the cursor position */
426 Bda->CursorPosition[0] = MAKEWORD(BiosSavedBufferInfo.dwCursorPosition.X,
427 BiosSavedBufferInfo.dwCursorPosition.Y);
428
429 /* Set the default video mode */
430 BiosSetVideoMode(BIOS_DEFAULT_VIDEO_MODE);
431
432 /* Set the console input mode */
433 SetConsoleMode(BiosConsoleInput, ENABLE_MOUSE_INPUT | ENABLE_PROCESSED_INPUT);
434
435 /* Initialize the PIC */
436 PicWriteCommand(PIC_MASTER_CMD, PIC_ICW1 | PIC_ICW1_ICW4);
437 PicWriteCommand(PIC_SLAVE_CMD, PIC_ICW1 | PIC_ICW1_ICW4);
438
439 /* Set the interrupt offsets */
440 PicWriteData(PIC_MASTER_DATA, BIOS_PIC_MASTER_INT);
441 PicWriteData(PIC_SLAVE_DATA, BIOS_PIC_SLAVE_INT);
442
443 /* Tell the master PIC there is a slave at IRQ 2 */
444 PicWriteData(PIC_MASTER_DATA, 1 << 2);
445 PicWriteData(PIC_SLAVE_DATA, 2);
446
447 /* Make sure the PIC is in 8086 mode */
448 PicWriteData(PIC_MASTER_DATA, PIC_ICW4_8086);
449 PicWriteData(PIC_SLAVE_DATA, PIC_ICW4_8086);
450
451 /* Clear the masks for both PICs */
452 PicWriteData(PIC_MASTER_DATA, 0x00);
453 PicWriteData(PIC_SLAVE_DATA, 0x00);
454
455 PitWriteCommand(0x34);
456 PitWriteData(0, 0x00);
457 PitWriteData(0, 0x00);
458
459 return TRUE;
460 }
461
462 VOID BiosCleanup()
463 {
464 /* Restore the old screen buffer */
465 SetConsoleActiveScreenBuffer(BiosConsoleOutput);
466
467 /* Restore the screen buffer size */
468 SetConsoleScreenBufferSize(BiosConsoleOutput, BiosSavedBufferInfo.dwSize);
469
470 /* Free the graphics buffer */
471 if (!VideoModes[CurrentVideoMode].Text) BiosDestroyGraphicsBuffer();
472
473 /* Close the console handles */
474 if (BiosConsoleInput != INVALID_HANDLE_VALUE) CloseHandle(BiosConsoleInput);
475 if (BiosConsoleOutput != INVALID_HANDLE_VALUE) CloseHandle(BiosConsoleOutput);
476 }
477
478 VOID BiosUpdateConsole(ULONG StartAddress, ULONG EndAddress)
479 {
480 ULONG i;
481 COORD Coordinates;
482 COORD Origin = { 0, 0 };
483 COORD UnitSize = { 1, 1 };
484 CHAR_INFO Character;
485 SMALL_RECT Rect;
486
487 /* Start from the character address */
488 StartAddress &= ~1;
489
490 if (VideoModes[CurrentVideoMode].Text)
491 {
492 /* Loop through all the addresses */
493 for (i = StartAddress; i < EndAddress; i += 2)
494 {
495 /* Get the coordinates */
496 Coordinates = BiosVideoAddressToCoord(i);
497
498 /* Make sure this is the current page */
499 if (BiosVideoAddressToPage(i) != CurrentVideoPage) continue;
500
501 /* Fill the rectangle structure */
502 Rect.Left = Coordinates.X;
503 Rect.Top = Coordinates.Y;
504 Rect.Right = Rect.Left;
505 Rect.Bottom = Rect.Top;
506
507 /* Fill the character data */
508 Character.Char.AsciiChar = *((PCHAR)((ULONG_PTR)BaseAddress + i));
509 Character.Attributes = *((PBYTE)((ULONG_PTR)BaseAddress + i + 1));
510
511 /* Write the character */
512 WriteConsoleOutputA(BiosConsoleOutput,
513 &Character,
514 UnitSize,
515 Origin,
516 &Rect);
517 }
518 }
519 else
520 {
521 /* Wait for the mutex object */
522 WaitForSingleObject(ConsoleMutex, INFINITE);
523
524 /* Copy the data to the framebuffer */
525 RtlCopyMemory((LPVOID)((ULONG_PTR)ConsoleFramebuffer
526 + StartAddress - BiosGetVideoMemoryStart()),
527 (LPVOID)((ULONG_PTR)BaseAddress + StartAddress),
528 EndAddress - StartAddress);
529
530 /* Release the mutex */
531 ReleaseMutex(ConsoleMutex);
532
533 /* Check if this is the first time the rectangle is updated */
534 if (!VideoNeedsUpdate)
535 {
536 UpdateRectangle.Left = UpdateRectangle.Top = (SHORT)0x7FFF;
537 UpdateRectangle.Right = UpdateRectangle.Bottom = (SHORT)0x8000;
538 }
539
540 /* Expand the update rectangle */
541 for (i = StartAddress; i < EndAddress; i++)
542 {
543 /* Get the coordinates */
544 Coordinates = BiosVideoAddressToCoord(i);
545
546 /* Expand the rectangle to include the point */
547 UpdateRectangle.Left = min(UpdateRectangle.Left, Coordinates.X);
548 UpdateRectangle.Right = max(UpdateRectangle.Right, Coordinates.X);
549 UpdateRectangle.Top = min(UpdateRectangle.Top, Coordinates.Y);
550 UpdateRectangle.Bottom = max(UpdateRectangle.Bottom, Coordinates.Y);
551 }
552
553 /* Set the update flag */
554 VideoNeedsUpdate = TRUE;
555 }
556 }
557
558 VOID BiosUpdateVideoMemory(ULONG StartAddress, ULONG EndAddress)
559 {
560 ULONG i;
561 COORD Coordinates;
562 WORD Attribute;
563 DWORD CharsWritten;
564
565 if (VideoModes[CurrentVideoMode].Text)
566 {
567 /* Loop through all the addresses */
568 for (i = StartAddress; i < EndAddress; i++)
569 {
570 /* Get the coordinates */
571 Coordinates = BiosVideoAddressToCoord(i);
572
573 /* Make sure this is the current page */
574 if (BiosVideoAddressToPage(i) != CurrentVideoPage) continue;
575
576 /* Check if this is a character byte or an attribute byte */
577 if ((i - BiosGetVideoMemoryStart()) % 2 == 0)
578 {
579 /* This is a regular character */
580 ReadConsoleOutputCharacterA(BiosConsoleOutput,
581 (LPSTR)((ULONG_PTR)BaseAddress + i),
582 sizeof(CHAR),
583 Coordinates,
584 &CharsWritten);
585 }
586 else
587 {
588 /* This is an attribute */
589 ReadConsoleOutputAttribute(BiosConsoleOutput,
590 &Attribute,
591 sizeof(CHAR),
592 Coordinates,
593 &CharsWritten);
594
595 *(PCHAR)((ULONG_PTR)BaseAddress + i) = LOBYTE(Attribute);
596 }
597 }
598 }
599 else
600 {
601 /* Wait for the mutex object */
602 WaitForSingleObject(ConsoleMutex, INFINITE);
603
604 /* Copy the data to the emulator memory */
605 RtlCopyMemory((LPVOID)((ULONG_PTR)BaseAddress + StartAddress),
606 (LPVOID)((ULONG_PTR)ConsoleFramebuffer
607 + StartAddress - BiosGetVideoMemoryStart()),
608 EndAddress - StartAddress);
609
610 /* Release the mutex */
611 ReleaseMutex(ConsoleMutex);
612 }
613 }
614
615 WORD BiosPeekCharacter()
616 {
617 WORD CharacterData;
618
619 /* Check if there is a key available */
620 if (Bda->KeybdBufferHead == Bda->KeybdBufferTail) return 0xFFFF;
621
622 /* Get the key from the queue, but don't remove it */
623 BiosKbdBufferTop(&CharacterData);
624
625 return CharacterData;
626 }
627
628 WORD BiosGetCharacter()
629 {
630 WORD CharacterData;
631 INPUT_RECORD InputRecord;
632 DWORD Count;
633
634 /* Check if there is a key available */
635 if (Bda->KeybdBufferHead != Bda->KeybdBufferTail)
636 {
637 /* Get the key from the queue, and remove it */
638 BiosKbdBufferTop(&CharacterData);
639 BiosKbdBufferPop();
640 }
641 else
642 {
643 while (TRUE)
644 {
645 /* Wait for a console event */
646 WaitForSingleObject(BiosConsoleInput, INFINITE);
647
648 /* Read the event, and make sure it's a keypress */
649 if (!ReadConsoleInput(BiosConsoleInput, &InputRecord, 1, &Count)) continue;
650 if (InputRecord.EventType != KEY_EVENT) continue;
651 if (!InputRecord.Event.KeyEvent.bKeyDown) continue;
652
653 /* Save the scan code and end the loop */
654 CharacterData = (InputRecord.Event.KeyEvent.wVirtualScanCode << 8)
655 | InputRecord.Event.KeyEvent.uChar.AsciiChar;
656
657 break;
658 }
659 }
660
661 return CharacterData;
662 }
663
664 VOID BiosVideoService()
665 {
666 INT i, CursorHeight;
667 BOOLEAN Invisible = FALSE;
668 COORD Position;
669 CONSOLE_CURSOR_INFO CursorInfo;
670 CHAR_INFO Character;
671 SMALL_RECT Rect;
672 DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
673 DWORD Ecx = EmulatorGetRegister(EMULATOR_REG_CX);
674 DWORD Edx = EmulatorGetRegister(EMULATOR_REG_DX);
675 DWORD Ebx = EmulatorGetRegister(EMULATOR_REG_BX);
676
677 switch (HIBYTE(Eax))
678 {
679 /* Set Video Mode */
680 case 0x00:
681 {
682 BiosSetVideoMode(LOBYTE(Eax));
683 break;
684 }
685
686 /* Set Text-Mode Cursor Shape */
687 case 0x01:
688 {
689 /* Retrieve and validate the input */
690 Invisible = ((HIBYTE(Ecx) >> 5) & 0x03) ? TRUE : FALSE;
691 CursorHeight = (HIBYTE(Ecx) & 0x1F) - (LOBYTE(Ecx) & 0x1F);
692 if (CursorHeight < 1) CursorHeight = 1;
693 if (CursorHeight > 100) CursorHeight = 100;
694
695 /* Update the BDA */
696 Bda->CursorStartLine = HIBYTE(Ecx);
697 Bda->CursorEndLine = LOBYTE(Ecx) & 0x1F;
698
699 /* Set the cursor */
700 CursorInfo.dwSize = (CursorHeight * 100) / CONSOLE_FONT_HEIGHT;
701 CursorInfo.bVisible = !Invisible;
702 SetConsoleCursorInfo(BiosConsoleOutput, &CursorInfo);
703
704 break;
705 }
706
707 /* Set Cursor Position */
708 case 0x02:
709 {
710 /* Make sure the selected video page exists */
711 if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
712
713 Bda->CursorPosition[HIBYTE(Ebx)] = LOWORD(Edx);
714
715 /* Check if this is the current video page */
716 if (HIBYTE(Ebx) == CurrentVideoPage)
717 {
718 /* Yes, change the actual cursor */
719 Position.X = LOBYTE(Edx);
720 Position.Y = HIBYTE(Edx);
721 SetConsoleCursorPosition(BiosConsoleOutput, Position);
722 }
723
724 break;
725 }
726
727 /* Get Cursor Position */
728 case 0x03:
729 {
730 /* Make sure the selected video page exists */
731 if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
732
733 /* Return the result */
734 EmulatorSetRegister(EMULATOR_REG_AX, 0);
735 EmulatorSetRegister(EMULATOR_REG_CX,
736 (Bda->CursorStartLine << 8) | Bda->CursorEndLine);
737 EmulatorSetRegister(EMULATOR_REG_DX, Bda->CursorPosition[HIBYTE(Ebx)]);
738
739 break;
740 }
741
742 /* Select Active Display Page */
743 case 0x05:
744 {
745 /* Check if the page exists */
746 if (LOBYTE(Eax) >= VideoModes[CurrentVideoMode].Pages) break;
747
748 /* Check if this is the same page */
749 if (LOBYTE(Eax) == CurrentVideoPage) break;
750
751 /* Change the video page */
752 BiosSetVideoPage(LOBYTE(Eax));
753
754 break;
755 }
756
757 /* Scroll Up/Down Window */
758 // TODO: Implement for different pages
759 case 0x06:
760 case 0x07:
761 {
762 BYTE Lines = LOBYTE(Eax);
763
764 Rect.Top = HIBYTE(Ecx);
765 Rect.Left = LOBYTE(Ecx);
766 Rect.Bottom = HIBYTE(Edx);
767 Rect.Right = LOBYTE(Edx);
768 Character.Char.UnicodeChar = L' ';
769 Character.Attributes = HIBYTE(Ebx);
770 Position.X = Rect.Left;
771
772 /* 0 means clear entire window */
773 if (Lines == 0) Lines = Rect.Bottom - Rect.Top;
774
775 if (HIBYTE(Eax) == 0x06) Position.Y = Rect.Top - Lines;
776 else Position.Y = Rect.Top + Lines;
777
778 ScrollConsoleScreenBuffer(BiosConsoleOutput,
779 &Rect,
780 &Rect,
781 Position,
782 &Character);
783
784 break;
785 }
786
787 /* Read Character And Attribute At Cursor Position */
788 case 0x08:
789 {
790 DWORD Address;
791
792 /* Make sure this is text mode */
793 if (!VideoModes[CurrentVideoMode].Text) break;
794
795 /* Make sure the selected video page exists */
796 if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
797
798 /* Find the address */
799 Address = BiosGetVideoMemoryStart()
800 + HIBYTE(Ebx) * BiosGetVideoPageSize()
801 + (HIBYTE(Bda->CursorPosition[HIBYTE(Ebx)])
802 * VideoModes[CurrentVideoMode].Height
803 + LOBYTE(Bda->CursorPosition[HIBYTE(Ebx)]))
804 * VideoModes[CurrentVideoMode].Bpp / 8;
805
806 /* Update the video memory at that address */
807 BiosUpdateVideoMemory(Address,
808 Address + VideoModes[CurrentVideoMode].Bpp / 8);
809
810 /* Return the result in AX */
811 EmulatorSetRegister(EMULATOR_REG_AX,
812 *((LPWORD)((ULONG_PTR)BaseAddress + Address)));
813
814 break;
815 }
816
817 /* Write Character And Attribute At Cursor Position */
818 case 0x09:
819 case 0x0A:
820 {
821 BYTE PixelSize = VideoModes[CurrentVideoMode].Bpp / 8;
822 WORD Data = (LOBYTE(Ebx) << 8) | LOBYTE(Eax);
823 WORD Repeat = LOWORD(Ecx);
824 DWORD Address = BiosGetVideoMemoryStart()
825 + CurrentVideoPage * BiosGetVideoPageSize()
826 + (HIBYTE(Bda->CursorPosition[CurrentVideoPage])
827 * VideoModes[CurrentVideoMode].Height
828 + LOBYTE(Bda->CursorPosition[CurrentVideoPage]))
829 * PixelSize;
830
831 /* Make sure this is text mode */
832 if (!VideoModes[CurrentVideoMode].Text) break;
833
834 /* Make sure the selected video page exists */
835 if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
836
837 /* Make sure we don't write over the end of video memory */
838 Repeat = min(Repeat,
839 (CONSOLE_VIDEO_MEM_END - Address)
840 / PixelSize);
841
842 /* Copy the values to the memory */
843 for (i = 0; i < Repeat; i++)
844 {
845 if (PixelSize == sizeof(BYTE) || HIBYTE(Eax) == 0x0A)
846 {
847 /* Just characters, no attributes */
848 *((LPBYTE)((ULONG_PTR)BaseAddress + Address) + i * PixelSize) = LOBYTE(Data);
849 }
850 else if (PixelSize == sizeof(WORD))
851 {
852 /* First byte for characters, second for attributes */
853 *((LPWORD)((ULONG_PTR)BaseAddress + Address) + i) = Data;
854 }
855 }
856
857 /* Update the range */
858 BiosUpdateConsole(Address,
859 Address + Repeat * (VideoModes[CurrentVideoMode].Bpp / 8));
860
861 break;
862 }
863
864 /* Teletype Output */
865 case 0x0E:
866 {
867 CHAR Character = LOBYTE(Eax);
868 DWORD NumWritten;
869
870 /* Make sure the page exists */
871 if (HIBYTE(Ebx) >= VideoModes[CurrentVideoMode].Pages) break;
872
873 /* Set the attribute */
874 SetConsoleTextAttribute(BiosConsoleOutput, LOBYTE(Ebx));
875
876 /* Write the character */
877 WriteConsoleA(BiosConsoleOutput,
878 &Character,
879 sizeof(CHAR),
880 &NumWritten,
881 NULL);
882
883 break;
884 }
885
886 /* Get Current Video Mode */
887 case 0x0F:
888 {
889 EmulatorSetRegister(EMULATOR_REG_AX,
890 MAKEWORD(Bda->VideoMode, Bda->ScreenColumns));
891 EmulatorSetRegister(EMULATOR_REG_BX,
892 MAKEWORD(LOBYTE(Ebx), Bda->VideoPage));
893
894 break;
895 }
896
897 /* Scroll Window */
898 case 0x12:
899 {
900 Rect.Top = HIBYTE(Ecx);
901 Rect.Left = LOBYTE(Ecx);
902 Rect.Bottom = HIBYTE(Edx);
903 Rect.Right = LOBYTE(Edx);
904 Character.Char.UnicodeChar = L' ';
905 Character.Attributes = 0x07;
906 Position.X = Rect.Left;
907 Position.Y = Rect.Top;
908
909 if (LOBYTE(Ebx) == 0) Position.Y -= LOBYTE(Eax);
910 else if (LOBYTE(Ebx) == 1) Position.Y += LOBYTE(Eax);
911 else if (LOBYTE(Ebx) == 2) Position.X -= LOBYTE(Eax);
912 else if (LOBYTE(Ebx) == 3) Position.X += LOBYTE(Eax);
913
914 ScrollConsoleScreenBuffer(BiosConsoleOutput,
915 &Rect,
916 &Rect,
917 Position,
918 &Character);
919
920 break;
921 }
922
923 default:
924 {
925 DPRINT1("BIOS Function INT 10h, AH = 0x%02X NOT IMPLEMENTED\n",
926 HIBYTE(Eax));
927 }
928 }
929 }
930
931 VOID BiosKeyboardService()
932 {
933 DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
934
935 switch (HIBYTE(Eax))
936 {
937 case 0x00:
938 {
939 /* Read the character (and wait if necessary) */
940 EmulatorSetRegister(EMULATOR_REG_AX, BiosGetCharacter());
941
942 break;
943 }
944
945 case 0x01:
946 {
947 WORD Data = BiosPeekCharacter();
948
949 if (Data != 0xFFFF)
950 {
951 /* There is a character, clear ZF and return it */
952 EmulatorSetRegister(EMULATOR_REG_AX, Data);
953 EmulatorClearFlag(EMULATOR_FLAG_ZF);
954 }
955 else
956 {
957 /* No character, set ZF */
958 EmulatorSetFlag(EMULATOR_FLAG_ZF);
959 }
960
961 break;
962 }
963
964 default:
965 {
966 DPRINT1("BIOS Function INT 16h, AH = 0x%02X NOT IMPLEMENTED\n",
967 HIBYTE(Eax));
968 }
969 }
970 }
971
972 VOID BiosTimeService()
973 {
974 DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
975 DWORD Ecx = EmulatorGetRegister(EMULATOR_REG_CX);
976 DWORD Edx = EmulatorGetRegister(EMULATOR_REG_DX);
977
978 switch (HIBYTE(Eax))
979 {
980 case 0x00:
981 {
982 /* Set AL to 1 if midnight had passed, 0 otherwise */
983 Eax &= 0xFFFFFF00;
984 if (Bda->MidnightPassed) Eax |= 1;
985
986 /* Return the tick count in CX:DX */
987 EmulatorSetRegister(EMULATOR_REG_AX, Eax);
988 EmulatorSetRegister(EMULATOR_REG_CX, HIWORD(Bda->TickCounter));
989 EmulatorSetRegister(EMULATOR_REG_DX, LOWORD(Bda->TickCounter));
990
991 /* Reset the midnight flag */
992 Bda->MidnightPassed = FALSE;
993
994 break;
995 }
996
997 case 0x01:
998 {
999 /* Set the tick count to CX:DX */
1000 Bda->TickCounter = MAKELONG(LOWORD(Edx), LOWORD(Ecx));
1001
1002 /* Reset the midnight flag */
1003 Bda->MidnightPassed = FALSE;
1004
1005 break;
1006 }
1007
1008 default:
1009 {
1010 DPRINT1("BIOS Function INT 1Ah, AH = 0x%02X NOT IMPLEMENTED\n",
1011 HIBYTE(Eax));
1012 }
1013 }
1014 }
1015
1016 VOID BiosSystemTimerInterrupt()
1017 {
1018 /* Increase the system tick count */
1019 Bda->TickCounter++;
1020 }
1021
1022 VOID BiosEquipmentService()
1023 {
1024 /* Return the equipment list */
1025 EmulatorSetRegister(EMULATOR_REG_AX, Bda->EquipmentList);
1026 }
1027
1028 VOID BiosHandleIrq(BYTE IrqNumber)
1029 {
1030 switch (IrqNumber)
1031 {
1032 /* PIT IRQ */
1033 case 0:
1034 {
1035 /* Perform the system timer interrupt */
1036 EmulatorInterrupt(BIOS_SYS_TIMER_INTERRUPT);
1037
1038 break;
1039 }
1040
1041 /* Keyboard IRQ */
1042 case 1:
1043 {
1044 BYTE ScanCode, VirtualKey;
1045 WORD Character;
1046
1047 /* Check if there is a scancode available */
1048 if (!(KeyboardReadStatus() & 1)) break;
1049
1050 /* Get the scan code and virtual key code */
1051 ScanCode = KeyboardReadData();
1052 VirtualKey = MapVirtualKey(ScanCode & 0x7F, MAPVK_VSC_TO_VK);
1053
1054 /* Check if this is a key press or release */
1055 if (!(ScanCode & (1 << 7)))
1056 {
1057 /* Key press */
1058 if (VirtualKey == VK_NUMLOCK
1059 || VirtualKey == VK_CAPITAL
1060 || VirtualKey == VK_SCROLL)
1061 {
1062 /* For toggle keys, toggle the lowest bit in the keyboard map */
1063 BiosKeyboardMap[VirtualKey] ^= ~(1 << 0);
1064 }
1065
1066 /* Set the highest bit */
1067 BiosKeyboardMap[VirtualKey] |= (1 << 7);
1068
1069 /* Find out which character this is */
1070 if (ToAscii(VirtualKey, ScanCode, BiosKeyboardMap, &Character, 0) > 0)
1071 {
1072 /* Push it onto the BIOS keyboard queue */
1073 BiosKbdBufferPush((ScanCode << 8) | (Character & 0xFF));
1074 }
1075 }
1076 else
1077 {
1078 /* Key release, unset the highest bit */
1079 BiosKeyboardMap[VirtualKey] &= ~(1 << 7);
1080 }
1081
1082 break;
1083 }
1084 }
1085
1086 /* Send End-of-Interrupt to the PIC */
1087 if (IrqNumber > 8) PicWriteCommand(PIC_SLAVE_CMD, PIC_OCW2_EOI);
1088 PicWriteCommand(PIC_MASTER_CMD, PIC_OCW2_EOI);
1089 }
1090
1091 /* EOF */