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