xref: /reactos/hal/halx86/generic/bios.c (revision 9cfd8dd9)
1 /*
2  * PROJECT:         ReactOS Hardware Abstraction Layer (HAL)
3  * LICENSE:         BSD - See COPYING.ARM in the top level directory
4  * PURPOSE:         BIOS Access Routines
5  * PROGRAMMERS:     ReactOS Portable Systems Group
6  *                  Alex Ionescu (alex.ionescu@reactos.org)
7  */
8 
9 /* INCLUDES *******************************************************************/
10 
11 #include <hal.h>
12 
13 #define NDEBUG
14 #include <debug.h>
15 
16 #include <setjmp.h>
17 
18 void __cdecl HalpTrap0D();
19 
20 /* GLOBALS ********************************************************************/
21 
22 //
23 // PTE Data
24 //
25 ULONG HalpSavedPfn;
26 HARDWARE_PTE HalpSavedPte;
27 
28 //
29 // IDT Data
30 //
31 PVOID HalpGpfHandler;
32 PVOID HalpBopHandler;
33 
34 //
35 // TSS Data
36 //
37 ULONG HalpSavedEsp0;
38 USHORT HalpSavedTss;
39 
40 //
41 // IOPM Data
42 //
43 USHORT HalpSavedIopmBase;
44 PUSHORT HalpSavedIoMap;
45 USHORT HalpSavedIoMapData[IOPM_SIZE / sizeof(USHORT)][2];
46 ULONG HalpSavedIoMapEntries;
47 
48 /* Where the protected mode stack is */
49 ULONG_PTR HalpSavedEsp;
50 
51 /* Where the real mode code ends */
52 extern PVOID HalpRealModeEnd;
53 
54 /* Context saved for return from v86 mode */
55 jmp_buf HalpSavedContext;
56 
57 
58 /* V86 OPCODE HANDLERS ********************************************************/
59 
60 BOOLEAN
61 FASTCALL
62 HalpOpcodeInvalid(IN PHAL_BIOS_FRAME BiosFrame)
63 {
64     PUCHAR Inst = (PUCHAR)(BiosFrame->CsBase + BiosFrame->Eip);
65 
66     /* Print error message */
67     DPRINT1("HAL: An invalid V86 opcode was encountered at address %X:%X\n"
68             "Opcode: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n",
69             BiosFrame->SegCs, BiosFrame->Eip,
70             Inst[0], Inst[1], Inst[2], Inst[3], Inst[4],
71             Inst[5], Inst[6], Inst[7], Inst[8], Inst[9]);
72 
73     /* Break */
74     DbgBreakPoint();
75     return FALSE;
76 }
77 
78 BOOLEAN
79 FASTCALL
80 HalpPushInt(IN PHAL_BIOS_FRAME BiosFrame,
81             IN ULONG Interrupt)
82 {
83     PUSHORT Stack;
84     ULONG Eip;
85 
86     /* Calculate stack address (SP) */
87     Stack = (PUSHORT)(BiosFrame->SsBase + (BiosFrame->Esp & 0xFFFF));
88 
89     /* Push EFlags */
90     Stack--;
91     *Stack = BiosFrame->EFlags & 0xFFFF;
92 
93     /* Push CS */
94     Stack--;
95     *Stack = BiosFrame->SegCs & 0xFFFF;
96 
97     /* Push IP */
98     Stack--;
99     *Stack = BiosFrame->Eip & 0xFFFF;
100 
101     /* Compute new CS:IP from the IVT address for this interrupt entry */
102     Eip = *(PULONG)(Interrupt * 4);
103     BiosFrame->Eip = Eip & 0xFFFF;
104     BiosFrame->SegCs = Eip >> 16;
105 
106     /* Update stack address */
107     BiosFrame->Esp = (ULONG_PTR)Stack & 0xFFFF;
108 
109     /* Update CS to linear */
110     BiosFrame->CsBase = BiosFrame->SegCs << 4;
111     BiosFrame->CsLimit = 0xFFFF;
112     BiosFrame->CsFlags = 0;
113 
114     /* We're done */
115     return TRUE;
116 }
117 
118 BOOLEAN
119 FASTCALL
120 HalpOpcodeINTnn(IN PHAL_BIOS_FRAME BiosFrame)
121 {
122     UCHAR Interrupt;
123     PKTRAP_FRAME TrapFrame;
124 
125     /* Convert SS to linear */
126     BiosFrame->SsBase = BiosFrame->SegSs << 4;
127     BiosFrame->SsLimit = 0xFFFF;
128     BiosFrame->SsFlags = 0;
129 
130     /* Increase EIP and validate */
131     BiosFrame->Eip++;
132     if (BiosFrame->Eip > BiosFrame->CsLimit) return FALSE;
133 
134     /* Read interrupt number */
135     Interrupt = *(PUCHAR)(BiosFrame->CsBase + BiosFrame->Eip);
136 
137     /* Increase EIP and push the interrupt */
138     BiosFrame->Eip++;
139     if (HalpPushInt(BiosFrame, Interrupt))
140     {
141         /* Update the trap frame */
142         TrapFrame = BiosFrame->TrapFrame;
143         TrapFrame->HardwareSegSs = BiosFrame->SegSs;
144         TrapFrame->HardwareEsp = BiosFrame->Esp;
145         TrapFrame->SegCs = BiosFrame->SegCs;
146         TrapFrame->EFlags = BiosFrame->EFlags;
147 
148         /* Success */
149         return TRUE;
150     }
151 
152     /* Failure */
153     return FALSE;
154 }
155 
156 BOOLEAN
157 FASTCALL
158 HalpDispatchV86Opcode(IN PKTRAP_FRAME TrapFrame)
159 {
160     UCHAR Instruction;
161     HAL_BIOS_FRAME BiosFrame;
162 
163     /* Fill out the BIOS frame */
164     BiosFrame.TrapFrame = TrapFrame;
165     BiosFrame.SegSs = TrapFrame->HardwareSegSs;
166     BiosFrame.Esp = TrapFrame->HardwareEsp;
167     BiosFrame.EFlags = TrapFrame->EFlags;
168     BiosFrame.SegCs = TrapFrame->SegCs;
169     BiosFrame.Eip = TrapFrame->Eip;
170     BiosFrame.Prefix = 0;
171 
172     /* Convert CS to linear */
173     BiosFrame.CsBase = BiosFrame.SegCs << 4;
174     BiosFrame.CsLimit = 0xFFFF;
175     BiosFrame.CsFlags = 0;
176 
177     /* Validate IP */
178     if (BiosFrame.Eip > BiosFrame.CsLimit) return FALSE;
179 
180     /* Read IP */
181     Instruction = *(PUCHAR)(BiosFrame.CsBase + BiosFrame.Eip);
182     if (Instruction != 0xCD)
183     {
184         /* We only support INT */
185         HalpOpcodeInvalid(&BiosFrame);
186         return FALSE;
187     }
188 
189     /* Handle the interrupt */
190     if (HalpOpcodeINTnn(&BiosFrame))
191     {
192         /* Update EIP */
193         TrapFrame->Eip = BiosFrame.Eip;
194 
195         /* We're done */
196         return TRUE;
197     }
198 
199     /* Failure */
200     return FALSE;
201 }
202 
203 /* V86 TRAP HANDLERS **********************************************************/
204 
205 DECLSPEC_NORETURN
206 VOID
207 FASTCALL
208 HalpTrap0DHandler(IN PKTRAP_FRAME TrapFrame)
209 {
210     /* Enter the trap */
211     KiEnterTrap(TrapFrame);
212 
213     /* Check if this is a V86 trap */
214     if (TrapFrame->EFlags & EFLAGS_V86_MASK)
215     {
216         /* Dispatch the opcode and exit the trap */
217         HalpDispatchV86Opcode(TrapFrame);
218         KiEoiHelper(TrapFrame);
219     }
220 
221     /* Strange, it isn't! This can happen during NMI */
222     DPRINT1("HAL: Trap0D while not in V86 mode\n");
223     KiDumpTrapFrame(TrapFrame);
224 
225     ERROR_FATAL();
226     while (TRUE); /* 'noreturn' function */
227 }
228 
229 VOID
230 DECLSPEC_NORETURN
231 HalpTrap06(VOID)
232 {
233     /* Restore ES/DS to known good values first */
234     Ke386SetEs(KGDT_R3_DATA | RPL_MASK);
235     Ke386SetDs(KGDT_R3_DATA | RPL_MASK);
236     Ke386SetFs(KGDT_R0_PCR);
237 
238     /* Restore the stack */
239     KeGetPcr()->TSS->Esp0 = HalpSavedEsp0;
240 
241     /* Return back to where we left */
242     longjmp(HalpSavedContext, 1);
243     UNREACHABLE;
244 }
245 
246 /* V8086 ENTER ****************************************************************/
247 
248 VOID
249 NTAPI
250 HalpBiosCall(VOID)
251 {
252     /* Must be volatile so it doesn't get optimized away! */
253     volatile KTRAP_FRAME V86TrapFrame;
254     ULONG_PTR StackOffset, CodeOffset;
255 
256     /* Save the context, check for return */
257     if (_setjmp(HalpSavedContext))
258     {
259         /* Returned from v86 */
260         return;
261     }
262 
263     /* Kill alignment faults */
264     __writecr0(__readcr0() & ~CR0_AM);
265 
266     /* Set new stack address */
267     KeGetPcr()->TSS->Esp0 = (ULONG)&V86TrapFrame - 0x20 - sizeof(FX_SAVE_AREA);
268 
269     /* Compute segmented IP and SP offsets */
270     StackOffset = (ULONG_PTR)&HalpRealModeEnd - 4 - (ULONG_PTR)HalpRealModeStart;
271     CodeOffset = (ULONG_PTR)HalpRealModeStart & 0xFFF;
272 
273     /* Now build the V86 trap frame */
274     V86TrapFrame.V86Es = 0;
275     V86TrapFrame.V86Ds = 0;
276     V86TrapFrame.V86Gs = 0;
277     V86TrapFrame.V86Fs = 0;
278     V86TrapFrame.HardwareSegSs = 0x2000;
279     V86TrapFrame.HardwareEsp = StackOffset + CodeOffset;
280     V86TrapFrame.EFlags = __readeflags() | EFLAGS_V86_MASK | EFLAGS_IOPL;
281     V86TrapFrame.SegCs = 0x2000;
282     V86TrapFrame.Eip = CodeOffset;
283 
284     /* Exit to V86 mode */
285     HalpExitToV86((PKTRAP_FRAME)&V86TrapFrame);
286 }
287 
288 /* FUNCTIONS ******************************************************************/
289 
290 VOID
291 NTAPI
292 HalpBorrowTss(VOID)
293 {
294     USHORT Tss;
295     PKGDTENTRY TssGdt;
296     ULONG_PTR TssLimit;
297     PKTSS TssBase;
298 
299     //
300     // Get the current TSS and its GDT entry
301     //
302     Tss = Ke386GetTr();
303     TssGdt = &KeGetPcr()->GDT[Tss / sizeof(KGDTENTRY)];
304 
305     //
306     // Get the KTSS limit and check if it has IOPM space
307     //
308     TssLimit = TssGdt->LimitLow | TssGdt->HighWord.Bits.LimitHi << 16;
309 
310     //
311     // If the KTSS doesn't have enough space this is probably an NMI or DF
312     //
313     if (TssLimit > IOPM_SIZE)
314     {
315         //
316         // We are good to go
317         //
318         HalpSavedTss = 0;
319         return;
320     }
321 
322     //
323     // Get the "real" TSS
324     //
325     TssGdt = &KeGetPcr()->GDT[KGDT_TSS / sizeof(KGDTENTRY)];
326     TssBase = (PKTSS)(ULONG_PTR)(TssGdt->BaseLow |
327                                  TssGdt->HighWord.Bytes.BaseMid << 16 |
328                                  TssGdt->HighWord.Bytes.BaseHi << 24);
329 
330     //
331     // Switch to it
332     //
333     KeGetPcr()->TSS = TssBase;
334 
335     //
336     // Set it up
337     //
338     TssGdt->HighWord.Bits.Type = I386_TSS;
339     TssGdt->HighWord.Bits.Pres = 1;
340     TssGdt->HighWord.Bits.Dpl = 0;
341 
342     //
343     // Load new TSS and return old one
344     //
345     Ke386SetTr(KGDT_TSS);
346     HalpSavedTss = Tss;
347 }
348 
349 VOID
350 NTAPI
351 HalpReturnTss(VOID)
352 {
353     PKGDTENTRY TssGdt;
354     PKTSS TssBase;
355 
356     //
357     // Get the original TSS
358     //
359     TssGdt = &KeGetPcr()->GDT[HalpSavedTss / sizeof(KGDTENTRY)];
360     TssBase = (PKTSS)(ULONG_PTR)(TssGdt->BaseLow |
361                                  TssGdt->HighWord.Bytes.BaseMid << 16 |
362                                  TssGdt->HighWord.Bytes.BaseHi << 24);
363 
364     //
365     // Switch to it
366     //
367     KeGetPcr()->TSS = TssBase;
368 
369     //
370     // Set it up
371     //
372     TssGdt->HighWord.Bits.Type = I386_TSS;
373     TssGdt->HighWord.Bits.Pres = 1;
374     TssGdt->HighWord.Bits.Dpl = 0;
375 
376     //
377     // Load old TSS
378     //
379     Ke386SetTr(HalpSavedTss);
380 }
381 
382 VOID
383 NTAPI
384 HalpStoreAndClearIopm(VOID)
385 {
386     USHORT i, j;
387     PUSHORT Entry = HalpSavedIoMap;
388 
389     //
390     // Loop the I/O Map
391     //
392     for (i = j = 0; i < IOPM_SIZE / sizeof(USHORT); i++)
393     {
394         //
395         // Check for non-FFFF entry
396         //
397         if (*Entry != 0xFFFF)
398         {
399             //
400             // Save it
401             //
402             HalpSavedIoMapData[j][0] = i;
403             HalpSavedIoMapData[j][1] = *Entry;
404             j++;
405         }
406 
407         //
408         // Clear it
409         //
410         *Entry++ = 0;
411     }
412 
413     //
414     // Terminate it
415     //
416     while (i++ < IOPM_FULL_SIZE / sizeof(USHORT))
417     {
418         *Entry++ = 0xFFFF;
419     }
420 
421     //
422     // Return the entries we saved
423     //
424     HalpSavedIoMapEntries = j;
425 }
426 
427 VOID
428 NTAPI
429 HalpRestoreIopm(VOID)
430 {
431     ULONG i = HalpSavedIoMapEntries;
432 
433     //
434     // Set default state
435     //
436     RtlFillMemory(HalpSavedIoMap, IOPM_FULL_SIZE, 0xFF);
437 
438     //
439     // Restore the backed up copy, and initialize it
440     //
441     while (i--) HalpSavedIoMap[HalpSavedIoMapData[i][0]] = HalpSavedIoMapData[i][1];
442 }
443 
444 VOID
445 NTAPI
446 HalpMapRealModeMemory(VOID)
447 {
448     PHARDWARE_PTE Pte, V86Pte;
449     ULONG i;
450 
451     //
452     // Get the page table directory for the lowest meg of memory
453     //
454     Pte = HalAddressToPde(0);
455     HalpSavedPfn = Pte->PageFrameNumber;
456     HalpSavedPte = *Pte;
457 
458     //
459     // Map it to the HAL reserved region and make it valid
460     //
461     Pte->Valid = 1;
462     Pte->Write = 1;
463     Pte->Owner = 1;
464     Pte->PageFrameNumber = (HalAddressToPde(0xFFC00000))->PageFrameNumber;
465 
466     //
467     // Flush the TLB
468     //
469     HalpFlushTLB();
470 
471     //
472     // Now loop the first meg of memory
473     //
474     for (i = 0; i < 0x100000; i += PAGE_SIZE)
475     {
476         //
477         // Identity map it
478         //
479         Pte = HalAddressToPte(i);
480         Pte->PageFrameNumber = i >> PAGE_SHIFT;
481         Pte->Valid = 1;
482         Pte->Write = 1;
483         Pte->Owner = 1;
484     }
485 
486     //
487     // Now get the entry for our real mode V86 code and the target
488     //
489     Pte = HalAddressToPte(0x20000);
490     V86Pte = HalAddressToPte(&HalpRealModeStart);
491     do
492     {
493         //
494         // Map the physical address into our real-mode region
495         //
496         Pte->PageFrameNumber = V86Pte->PageFrameNumber;
497 
498         //
499         // Keep going until we've reached the end of our region
500         //
501         Pte++;
502         V86Pte++;
503     } while (V86Pte <= HalAddressToPte(&HalpRealModeEnd));
504 
505     //
506     // Flush the TLB
507     //
508     HalpFlushTLB();
509 }
510 
511 VOID
512 NTAPI
513 HalpSwitchToRealModeTrapHandlers(VOID)
514 {
515     //
516     // Save the current Invalid Opcode and General Protection Fault Handlers
517     //
518     HalpGpfHandler = KeQueryInterruptHandler(13);
519     HalpBopHandler = KeQueryInterruptHandler(6);
520 
521     //
522     // Now set our own GPF handler to handle exceptions while in real mode
523     //
524     KeRegisterInterruptHandler(13, HalpTrap0D);
525 
526     //
527     // And our own invalid opcode handler to detect the BOP to get us out
528     //
529     KeRegisterInterruptHandler(6, HalpTrap06);
530 }
531 
532 VOID
533 NTAPI
534 HalpSetupRealModeIoPermissionsAndTask(VOID)
535 {
536     //
537     // Switch to valid TSS
538     //
539     HalpBorrowTss();
540 
541     //
542     // Save a copy of the I/O Map and delete it
543     //
544     HalpSavedIoMap = (PUSHORT)KeGetPcr()->TSS->IoMaps[0].IoMap;
545     HalpStoreAndClearIopm();
546 
547     //
548     // Save the IOPM and switch to the real-mode one
549     //
550     HalpSavedIopmBase = KeGetPcr()->TSS->IoMapBase;
551     KeGetPcr()->TSS->IoMapBase = KiComputeIopmOffset(1);
552 
553     //
554     // Save our stack pointer
555     //
556     HalpSavedEsp0 = KeGetPcr()->TSS->Esp0;
557 }
558 
559 VOID
560 NTAPI
561 HalpRestoreTrapHandlers(VOID)
562 {
563     //
564     // Keep dummy GPF handler in case we get an NMI during V8086
565     //
566     if (!HalpNMIInProgress)
567     {
568         //
569         // Not an NMI -- put back the original handler
570         //
571         KeRegisterInterruptHandler(13, HalpGpfHandler);
572     }
573 
574     //
575     // Restore invalid opcode handler
576     //
577     KeRegisterInterruptHandler(6, HalpBopHandler);
578 }
579 
580 VOID
581 NTAPI
582 HalpRestoreIoPermissionsAndTask(VOID)
583 {
584     //
585     // Restore the stack pointer
586     //
587     KeGetPcr()->TSS->Esp0 = HalpSavedEsp0;
588 
589     //
590     // Restore the I/O Map
591     //
592     HalpRestoreIopm();
593 
594     //
595     // Restore the IOPM
596     //
597     KeGetPcr()->TSS->IoMapBase = HalpSavedIopmBase;
598 
599     //
600     // Restore the TSS
601     //
602     if (HalpSavedTss) HalpReturnTss();
603 }
604 
605 VOID
606 NTAPI
607 HalpUnmapRealModeMemory(VOID)
608 {
609     ULONG i;
610     PHARDWARE_PTE Pte;
611 
612     //
613     // Loop the first meg of memory
614     //
615     for (i = 0; i < 0x100000; i += PAGE_SIZE)
616     {
617         //
618         // Invalidate each PTE
619         //
620         Pte = HalAddressToPte(i);
621         Pte->Valid = 0;
622         Pte->Write = 0;
623         Pte->Owner = 0;
624         Pte->PageFrameNumber = 0;
625     }
626 
627     //
628     // Restore the PDE for the lowest megabyte of memory
629     //
630     Pte = HalAddressToPde(0);
631     *Pte = HalpSavedPte;
632     Pte->PageFrameNumber = HalpSavedPfn;
633 
634     //
635     // Flush the TLB
636     //
637     HalpFlushTLB();
638 }
639 
640 BOOLEAN
641 NTAPI
642 HalpBiosDisplayReset(VOID)
643 {
644 #if defined(SARCH_XBOX) || defined(SARCH_PC98)
645     /* There is no VGA BIOS on these machine types */
646     return FALSE;
647 #else
648     ULONG Flags;
649     PHARDWARE_PTE IdtPte;
650     BOOLEAN RestoreWriteProtection = FALSE;
651 
652     //
653     // Disable interrupts
654     //
655     Flags = __readeflags();
656     _disable();
657 
658     //
659     // Map memory available to the V8086 real-mode code
660     //
661     HalpMapRealModeMemory();
662 
663     //
664     // On P5, the first 7 entries of the IDT are write protected to work around
665     // the cmpxchg8b lock errata. Unprotect them here so we can set our custom
666     // invalid op-code handler.
667     //
668     IdtPte = HalAddressToPte(KeGetPcr()->IDT);
669     RestoreWriteProtection = IdtPte->Write != 0;
670     IdtPte->Write = 1;
671 
672     //
673     // Use special invalid opcode and GPF trap handlers
674     //
675     HalpSwitchToRealModeTrapHandlers();
676 
677     //
678     // Configure the IOPM and TSS
679     //
680     HalpSetupRealModeIoPermissionsAndTask();
681 
682     //
683     // Now jump to real mode
684     //
685     HalpBiosCall();
686 
687     //
688     // Restore kernel trap handlers
689     //
690     HalpRestoreTrapHandlers();
691 
692     //
693     // Restore write permission
694     //
695     IdtPte->Write = RestoreWriteProtection;
696 
697     //
698     // Restore TSS and IOPM
699     //
700     HalpRestoreIoPermissionsAndTask();
701 
702     //
703     // Restore low memory mapping
704     //
705     HalpUnmapRealModeMemory();
706 
707     //
708     // Restore interrupts if they were previously enabled
709     //
710     __writeeflags(Flags);
711     return TRUE;
712 #endif
713 }
714 
715 /* EOF */
716