[NTVDM]
[reactos.git] / subsystems / ntvdm / bios.c
1 /*
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: bios.c
5 * PURPOSE: VDM BIOS
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 */
8
9 /* INCLUDES *******************************************************************/
10
11 #include "bios.h"
12 #include "emulator.h"
13 #include "pic.h"
14 #include "ps2.h"
15 #include "timer.h"
16
17 /* PRIVATE VARIABLES **********************************************************/
18
19 static BYTE CursorRow, CursorCol;
20 static WORD ConsoleWidth, ConsoleHeight;
21 static BYTE BiosKeyboardMap[256];
22 static WORD BiosKbdBuffer[BIOS_KBD_BUFFER_SIZE];
23 static UINT BiosKbdBufferStart = 0, BiosKbdBufferEnd = 0;
24 static BOOLEAN BiosKbdBufferEmpty = TRUE;
25
26 /* PRIVATE FUNCTIONS **********************************************************/
27
28 static COORD BiosVideoAddressToCoord(ULONG Address)
29 {
30 COORD Result = {0, 0};
31 CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo;
32 HANDLE ConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
33
34 if (!GetConsoleScreenBufferInfo(ConsoleOutput, &ConsoleInfo))
35 {
36 ASSERT(FALSE);
37 return Result;
38 }
39
40 Result.X = ((Address - CONSOLE_VIDEO_MEM_START) >> 1) % ConsoleInfo.dwSize.X;
41 Result.Y = ((Address - CONSOLE_VIDEO_MEM_START) >> 1) / ConsoleInfo.dwSize.X;
42
43 return Result;
44 }
45
46 static BOOLEAN BiosKbdBufferPush(WORD Data)
47 {
48 /* If it's full, fail */
49 if (!BiosKbdBufferEmpty && (BiosKbdBufferStart == BiosKbdBufferEnd))
50 {
51 return FALSE;
52 }
53
54 /* Otherwise, add the value to the queue */
55 BiosKbdBuffer[BiosKbdBufferEnd] = Data;
56 BiosKbdBufferEnd++;
57 BiosKbdBufferEnd %= BIOS_KBD_BUFFER_SIZE;
58 BiosKbdBufferEmpty = FALSE;
59
60 /* Return success */
61 return TRUE;
62 }
63
64 static BOOLEAN BiosKbdBufferTop(LPWORD Data)
65 {
66 /* If it's empty, fail */
67 if (BiosKbdBufferEmpty) return FALSE;
68
69 /* Otherwise, get the value and return success */
70 *Data = BiosKbdBuffer[BiosKbdBufferStart];
71 return TRUE;
72 }
73
74 static BOOLEAN BiosKbdBufferPop()
75 {
76 /* If it's empty, fail */
77 if (BiosKbdBufferEmpty) return FALSE;
78
79 /* Otherwise, remove the value and return success */
80 BiosKbdBufferStart++;
81 BiosKbdBufferStart %= BIOS_KBD_BUFFER_SIZE;
82 if (BiosKbdBufferStart == BiosKbdBufferEnd) BiosKbdBufferEmpty = TRUE;
83
84 return TRUE;
85 }
86
87 /* PUBLIC FUNCTIONS ***********************************************************/
88
89 BOOLEAN BiosInitialize()
90 {
91 INT i;
92 WORD Offset = 0;
93 HANDLE ConsoleInput = GetStdHandle(STD_INPUT_HANDLE);
94 HANDLE ConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
95 CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo;
96 LPWORD IntVecTable = (LPWORD)((ULONG_PTR)BaseAddress);
97 LPBYTE BiosCode = (LPBYTE)((ULONG_PTR)BaseAddress + TO_LINEAR(BIOS_SEGMENT, 0));
98
99 /* Generate ISR stubs and fill the IVT */
100 for (i = 0; i < 256; i++)
101 {
102 IntVecTable[i * 2] = Offset;
103 IntVecTable[i * 2 + 1] = BIOS_SEGMENT;
104
105 if (i != SPECIAL_INT_NUM)
106 {
107 BiosCode[Offset++] = 0xFA; // cli
108
109 BiosCode[Offset++] = 0x6A; // push i
110 BiosCode[Offset++] = (BYTE)i;
111
112 BiosCode[Offset++] = 0xCD; // int SPECIAL_INT_NUM
113 BiosCode[Offset++] = SPECIAL_INT_NUM;
114
115 BiosCode[Offset++] = 0x83; // add sp, 2
116 BiosCode[Offset++] = 0xC4;
117 BiosCode[Offset++] = 0x02;
118 }
119
120 BiosCode[Offset++] = 0xCF; // iret
121 }
122
123 /* Get the console buffer info */
124 if (!GetConsoleScreenBufferInfo(ConsoleOutput, &ConsoleInfo))
125 {
126 return FALSE;
127 }
128
129 /* Set the initial cursor position and console size */
130 CursorCol = ConsoleInfo.dwCursorPosition.X;
131 CursorRow = ConsoleInfo.dwCursorPosition.Y;
132 ConsoleWidth = ConsoleInfo.dwSize.X;
133 ConsoleHeight = ConsoleInfo.dwSize.Y;
134
135 /* Set the console input mode */
136 SetConsoleMode(ConsoleInput, ENABLE_MOUSE_INPUT | ENABLE_PROCESSED_INPUT);
137
138 /* Initialize the PIC */
139 PicWriteCommand(PIC_MASTER_CMD, PIC_ICW1 | PIC_ICW1_ICW4);
140 PicWriteCommand(PIC_SLAVE_CMD, PIC_ICW1 | PIC_ICW1_ICW4);
141
142 /* Set the interrupt offsets */
143 PicWriteData(PIC_MASTER_DATA, BIOS_PIC_MASTER_INT);
144 PicWriteData(PIC_SLAVE_DATA, BIOS_PIC_SLAVE_INT);
145
146 /* Tell the master PIC there is a slave at IRQ 2 */
147 PicWriteData(PIC_MASTER_DATA, 1 << 2);
148 PicWriteData(PIC_SLAVE_DATA, 2);
149
150 /* Make sure the PIC is in 8086 mode */
151 PicWriteData(PIC_MASTER_DATA, PIC_ICW4_8086);
152 PicWriteData(PIC_SLAVE_DATA, PIC_ICW4_8086);
153
154 /* Clear the masks for both PICs */
155 PicWriteData(PIC_MASTER_DATA, 0x00);
156 PicWriteData(PIC_SLAVE_DATA, 0x00);
157
158 PitWriteCommand(0x34);
159 PitWriteData(0, 0x00);
160 PitWriteData(0, 0x00);
161
162 return TRUE;
163 }
164
165 VOID BiosUpdateConsole(ULONG StartAddress, ULONG EndAddress)
166 {
167 ULONG i;
168 COORD Coordinates;
169 DWORD CharsWritten;
170 HANDLE ConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
171
172 /* Loop through all the addresses */
173 for (i = StartAddress; i < EndAddress; i++)
174 {
175 /* Get the coordinates */
176 Coordinates = BiosVideoAddressToCoord(i);
177
178 /* Check if this is a character byte or an attribute byte */
179 if ((i - CONSOLE_VIDEO_MEM_START) % 2 == 0)
180 {
181 /* This is a regular character */
182 FillConsoleOutputCharacterA(ConsoleOutput,
183 *(PCHAR)((ULONG_PTR)BaseAddress + i),
184 sizeof(CHAR),
185 Coordinates,
186 &CharsWritten);
187 }
188 else
189 {
190 /* This is an attribute */
191 FillConsoleOutputAttribute(ConsoleOutput,
192 *(PCHAR)((ULONG_PTR)BaseAddress + i),
193 sizeof(CHAR),
194 Coordinates,
195 &CharsWritten);
196 }
197 }
198 }
199
200 VOID BiosUpdateVideoMemory(ULONG StartAddress, ULONG EndAddress)
201 {
202 ULONG i;
203 COORD Coordinates;
204 WORD Attribute;
205 DWORD CharsWritten;
206 HANDLE ConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
207
208 /* Loop through all the addresses */
209 for (i = StartAddress; i < EndAddress; i++)
210 {
211 /* Get the coordinates */
212 Coordinates = BiosVideoAddressToCoord(i);
213
214 /* Check if this is a character byte or an attribute byte */
215 if ((i - CONSOLE_VIDEO_MEM_START) % 2 == 0)
216 {
217 /* This is a regular character */
218 ReadConsoleOutputCharacterA(ConsoleOutput,
219 (LPSTR)((ULONG_PTR)BaseAddress + i),
220 sizeof(CHAR),
221 Coordinates,
222 &CharsWritten);
223 }
224 else
225 {
226 /* This is an attribute */
227 ReadConsoleOutputAttribute(ConsoleOutput,
228 &Attribute,
229 sizeof(CHAR),
230 Coordinates,
231 &CharsWritten);
232
233 *(PCHAR)((ULONG_PTR)BaseAddress + i) = LOBYTE(Attribute);
234 }
235 }
236 }
237
238 WORD BiosPeekCharacter()
239 {
240 WORD CharacterData;
241
242 /* Check if there is a key available */
243 if (BiosKbdBufferEmpty) return 0xFFFF;
244
245 /* Get the key from the queue, but don't remove it */
246 BiosKbdBufferTop(&CharacterData);
247
248 return CharacterData;
249 }
250
251 WORD BiosGetCharacter()
252 {
253 WORD CharacterData;
254 HANDLE ConsoleInput = GetStdHandle(STD_INPUT_HANDLE);
255 INPUT_RECORD InputRecord;
256 DWORD Count;
257
258 /* Check if there is a key available */
259 if (!BiosKbdBufferEmpty)
260 {
261 /* Get the key from the queue, and remove it */
262 BiosKbdBufferTop(&CharacterData);
263 BiosKbdBufferPop();
264 }
265 else
266 {
267 while (TRUE)
268 {
269 /* Wait for a console event */
270 WaitForSingleObject(ConsoleInput, INFINITE);
271
272 /* Read the event, and make sure it's a keypress */
273 if (!ReadConsoleInput(ConsoleInput, &InputRecord, 1, &Count)) continue;
274 if (InputRecord.EventType != KEY_EVENT) continue;
275 if (!InputRecord.Event.KeyEvent.bKeyDown) continue;
276
277 /* Save the scan code and end the loop */
278 CharacterData = (InputRecord.Event.KeyEvent.wVirtualScanCode << 8)
279 | InputRecord.Event.KeyEvent.uChar.AsciiChar;
280
281 break;
282 }
283 }
284
285 return CharacterData;
286 }
287
288 VOID BiosVideoService()
289 {
290 HANDLE ConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
291 INT CursorHeight;
292 BOOLEAN Invisible = FALSE;
293 COORD Position;
294 CONSOLE_CURSOR_INFO CursorInfo;
295 CHAR_INFO Character;
296 SMALL_RECT Rect;
297 DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
298 DWORD Ecx = EmulatorGetRegister(EMULATOR_REG_CX);
299 DWORD Edx = EmulatorGetRegister(EMULATOR_REG_DX);
300 DWORD Ebx = EmulatorGetRegister(EMULATOR_REG_BX);
301
302 switch (HIBYTE(Eax))
303 {
304 /* Set Text-Mode Cursor Shape */
305 case 0x01:
306 {
307 /* Retrieve and validate the input */
308 Invisible = ((HIBYTE(Ecx) >> 5) & 0x03) ? TRUE : FALSE;
309 CursorHeight = (HIBYTE(Ecx) & 0x1F) - (LOBYTE(Ecx) & 0x1F);
310 if (CursorHeight < 1) CursorHeight = 1;
311 if (CursorHeight > 100) CursorHeight = 100;
312
313 /* Set the cursor */
314 CursorInfo.dwSize = (CursorHeight * 100) / CONSOLE_FONT_HEIGHT;
315 CursorInfo.bVisible = !Invisible;
316 SetConsoleCursorInfo(ConsoleOutput, &CursorInfo);
317
318 break;
319 }
320
321 /* Set Cursor Position */
322 case 0x02:
323 {
324 Position.X = LOBYTE(Edx);
325 Position.Y = HIBYTE(Edx);
326
327 SetConsoleCursorPosition(ConsoleOutput, Position);
328 break;
329 }
330
331 /* Scroll Up/Down Window */
332 case 0x06:
333 case 0x07:
334 {
335 Rect.Top = HIBYTE(Ecx);
336 Rect.Left = LOBYTE(Ecx);
337 Rect.Bottom = HIBYTE(Edx);
338 Rect.Right = LOBYTE(Edx);
339 Character.Char.UnicodeChar = L' ';
340 Character.Attributes = HIBYTE(Ebx);
341 Position.X = Rect.Left;
342 if (HIBYTE(Eax) == 0x06) Position.Y = Rect.Top - LOBYTE(Eax);
343 else Position.Y = Rect.Top + LOBYTE(Eax);
344
345 ScrollConsoleScreenBuffer(ConsoleOutput,
346 &Rect,
347 &Rect,
348 Position,
349 &Character);
350 break;
351 }
352
353 /* Read Character And Attribute At Cursor Position */
354 case 0x08:
355 {
356 break;
357 }
358
359 /* Write Character And Attribute At Cursor Position */
360 case 0x09:
361 {
362 break;
363 }
364
365 /* Write Character Only At Cursor Position */
366 case 0x0A:
367 {
368 break;
369 }
370 }
371 }
372
373 VOID BiosKeyboardService()
374 {
375 DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
376
377 switch (HIBYTE(Eax))
378 {
379 case 0x00:
380 {
381 /* Read the character (and wait if necessary) */
382 EmulatorSetRegister(EMULATOR_REG_AX, BiosGetCharacter());
383
384 break;
385 }
386
387 case 0x01:
388 {
389 WORD Data = BiosPeekCharacter();
390
391 if (Data != 0xFFFF)
392 {
393 /* There is a character, clear ZF and return it */
394 EmulatorSetRegister(EMULATOR_REG_AX, Data);
395 EmulatorClearFlag(EMULATOR_FLAG_ZF);
396 }
397 else
398 {
399 /* No character, set ZF */
400 EmulatorSetFlag(EMULATOR_FLAG_ZF);
401 }
402
403 break;
404 }
405 }
406 }
407
408 VOID BiosHandleIrq(BYTE IrqNumber)
409 {
410 switch (IrqNumber)
411 {
412 /* PIT IRQ */
413 case 0:
414 {
415 /* Perform the system timer interrupt */
416 EmulatorInterrupt(0x1C);
417
418 break;
419 }
420
421 /* Keyboard IRQ */
422 case 1:
423 {
424 BYTE ScanCode, VirtualKey;
425 WORD Character;
426
427 /* Check if there is a scancode available */
428 if (!(KeyboardReadStatus() & 1)) break;
429
430 /* Get the scan code and virtual key code */
431 ScanCode = KeyboardReadData();
432 VirtualKey = MapVirtualKey(ScanCode, MAPVK_VSC_TO_VK);
433
434 /* Check if this is a key press or release */
435 if (!(ScanCode & (1 << 7)))
436 {
437 /* Key press */
438 if (VirtualKey == VK_NUMLOCK
439 || VirtualKey == VK_CAPITAL
440 || VirtualKey == VK_SCROLL)
441 {
442 /* For toggle keys, toggle the lowest bit in the keyboard map */
443 BiosKeyboardMap[VirtualKey] ^= ~(1 << 0);
444 }
445
446 /* Set the highest bit */
447 BiosKeyboardMap[VirtualKey] |= (1 << 7);
448
449 /* Find out which character this is */
450 ToAscii(ScanCode, VirtualKey, BiosKeyboardMap, &Character, 0);
451
452 /* Push it onto the BIOS keyboard queue */
453 BiosKbdBufferPush((ScanCode << 8) | (Character & 0xFF));
454 }
455 else
456 {
457 /* Key release, unset the highest bit */
458 BiosKeyboardMap[VirtualKey] &= ~(1 << 7);
459 }
460
461 break;
462 }
463 }
464
465 /* Send End-of-Interrupt to the PIC */
466 if (IrqNumber > 8) PicWriteCommand(PIC_SLAVE_CMD, PIC_OCW2_EOI);
467 PicWriteCommand(PIC_MASTER_CMD, PIC_OCW2_EOI);
468 }
469
470 /* EOF */