1 /** @file
2   Call into 16-bit BIOS code, Use AsmThunk16 function of BaseLib.
3 
4 Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR>
5 
6 SPDX-License-Identifier: BSD-2-Clause-Patent
7 
8 **/
9 
10 #include "LegacyBiosInterface.h"
11 
12 THUNK_CONTEXT      mThunkContext;
13 
14 /**
15   Sets the counter value for Timer #0 in a legacy 8254 timer.
16 
17   @param  Count - The 16-bit counter value to program into Timer #0 of the legacy 8254 timer.
18 
19 **/
20 VOID
SetPitCount(IN UINT16 Count)21 SetPitCount (
22   IN UINT16  Count
23   )
24 {
25   IoWrite8 (TIMER_CONTROL_PORT, TIMER0_CONTROL_WORD);
26   IoWrite8 (TIMER0_COUNT_PORT, (UINT8) (Count & 0xFF));
27   IoWrite8 (TIMER0_COUNT_PORT, (UINT8) ((Count>>8) & 0xFF));
28 }
29 
30 /**
31   Thunk to 16-bit real mode and execute a software interrupt with a vector
32   of BiosInt. Regs will contain the 16-bit register context on entry and
33   exit.
34 
35   @param  This    Protocol instance pointer.
36   @param  BiosInt Processor interrupt vector to invoke
37   @param  Regs    Register contexted passed into (and returned) from thunk to
38                   16-bit mode
39 
40   @retval FALSE   Thunk completed, and there were no BIOS errors in the target code.
41                   See Regs for status.
42   @retval TRUE    There was a BIOS erro in the target code.
43 
44 **/
45 BOOLEAN
46 EFIAPI
LegacyBiosInt86(IN EFI_LEGACY_BIOS_PROTOCOL * This,IN UINT8 BiosInt,IN EFI_IA32_REGISTER_SET * Regs)47 LegacyBiosInt86 (
48   IN  EFI_LEGACY_BIOS_PROTOCOL      *This,
49   IN  UINT8                         BiosInt,
50   IN  EFI_IA32_REGISTER_SET         *Regs
51   )
52 {
53   UINT16                Segment;
54   UINT16                Offset;
55 
56   Regs->X.Flags.Reserved1 = 1;
57   Regs->X.Flags.Reserved2 = 0;
58   Regs->X.Flags.Reserved3 = 0;
59   Regs->X.Flags.Reserved4 = 0;
60   Regs->X.Flags.IOPL      = 3;
61   Regs->X.Flags.NT        = 0;
62   Regs->X.Flags.IF        = 0;
63   Regs->X.Flags.TF        = 0;
64   Regs->X.Flags.CF        = 0;
65   //
66   // The base address of legacy interrupt vector table is 0.
67   // We use this base address to get the legacy interrupt handler.
68   //
69   ACCESS_PAGE0_CODE (
70     Segment               = (UINT16)(((UINT32 *)0)[BiosInt] >> 16);
71     Offset                = (UINT16)((UINT32 *)0)[BiosInt];
72   );
73 
74   return InternalLegacyBiosFarCall (
75            This,
76            Segment,
77            Offset,
78            Regs,
79            &Regs->X.Flags,
80            sizeof (Regs->X.Flags)
81            );
82 }
83 
84 /**
85   Thunk to 16-bit real mode and call Segment:Offset. Regs will contain the
86   16-bit register context on entry and exit. Arguments can be passed on
87   the Stack argument
88 
89   @param  This                   Protocol instance pointer.
90   @param  Segment                Segemnt of 16-bit mode call
91   @param  Offset                 Offset of 16-bit mdoe call
92   @param  Regs                   Register contexted passed into (and returned) from
93                                  thunk to  16-bit mode
94   @param  Stack                  Caller allocated stack used to pass arguments
95   @param  StackSize              Size of Stack in bytes
96 
97   @retval FALSE                  Thunk completed, and there were no BIOS errors in
98                                  the target code. See Regs for status.
99   @retval TRUE                   There was a BIOS erro in the target code.
100 
101 **/
102 BOOLEAN
103 EFIAPI
LegacyBiosFarCall86(IN EFI_LEGACY_BIOS_PROTOCOL * This,IN UINT16 Segment,IN UINT16 Offset,IN EFI_IA32_REGISTER_SET * Regs,IN VOID * Stack,IN UINTN StackSize)104 LegacyBiosFarCall86 (
105   IN  EFI_LEGACY_BIOS_PROTOCOL        *This,
106   IN  UINT16                          Segment,
107   IN  UINT16                          Offset,
108   IN  EFI_IA32_REGISTER_SET           *Regs,
109   IN  VOID                            *Stack,
110   IN  UINTN                           StackSize
111   )
112 {
113   Regs->X.Flags.Reserved1 = 1;
114   Regs->X.Flags.Reserved2 = 0;
115   Regs->X.Flags.Reserved3 = 0;
116   Regs->X.Flags.Reserved4 = 0;
117   Regs->X.Flags.IOPL      = 3;
118   Regs->X.Flags.NT        = 0;
119   Regs->X.Flags.IF        = 1;
120   Regs->X.Flags.TF        = 0;
121   Regs->X.Flags.CF        = 0;
122 
123   return InternalLegacyBiosFarCall (This, Segment, Offset, Regs, Stack, StackSize);
124 }
125 
126 /**
127   Provide NULL interrupt handler which is used to check
128   if there is more than one HW interrupt registers with the CPU AP.
129 
130   @param  InterruptType - The type of interrupt that occured
131   @param  SystemContext - A pointer to the system context when the interrupt occured
132 
133 **/
134 VOID
135 EFIAPI
LegacyBiosNullInterruptHandler(IN EFI_EXCEPTION_TYPE InterruptType,IN EFI_SYSTEM_CONTEXT SystemContext)136 LegacyBiosNullInterruptHandler (
137   IN EFI_EXCEPTION_TYPE   InterruptType,
138   IN EFI_SYSTEM_CONTEXT   SystemContext
139   )
140 {
141 }
142 
143 /**
144   Thunk to 16-bit real mode and call Segment:Offset. Regs will contain the
145   16-bit register context on entry and exit. Arguments can be passed on
146   the Stack argument
147 
148   @param  This       Protocol instance pointer.
149   @param  Segment    Segemnt of 16-bit mode call
150   @param  Offset     Offset of 16-bit mdoe call
151   @param  Regs       Register contexted passed into (and returned) from thunk to
152                      16-bit mode
153   @param  Stack      Caller allocated stack used to pass arguments
154   @param  StackSize  Size of Stack in bytes
155 
156   @retval FALSE      Thunk completed, and there were no BIOS errors in the target code.
157                      See Regs for status.
158   @retval TRUE       There was a BIOS erro in the target code.
159 
160 **/
161 BOOLEAN
162 EFIAPI
InternalLegacyBiosFarCall(IN EFI_LEGACY_BIOS_PROTOCOL * This,IN UINT16 Segment,IN UINT16 Offset,IN EFI_IA32_REGISTER_SET * Regs,IN VOID * Stack,IN UINTN StackSize)163 InternalLegacyBiosFarCall (
164   IN  EFI_LEGACY_BIOS_PROTOCOL        *This,
165   IN  UINT16                          Segment,
166   IN  UINT16                          Offset,
167   IN  EFI_IA32_REGISTER_SET           *Regs,
168   IN  VOID                            *Stack,
169   IN  UINTN                           StackSize
170   )
171 {
172   UINTN                 Status;
173   LEGACY_BIOS_INSTANCE  *Private;
174   UINT16                *Stack16;
175   EFI_TPL               OriginalTpl;
176   IA32_REGISTER_SET     ThunkRegSet;
177   BOOLEAN               InterruptState;
178   UINT64                TimerPeriod;
179 
180   Private = LEGACY_BIOS_INSTANCE_FROM_THIS (This);
181 
182   ZeroMem (&ThunkRegSet, sizeof (ThunkRegSet));
183   ThunkRegSet.X.DI   = Regs->X.DI;
184   ThunkRegSet.X.SI   = Regs->X.SI;
185   ThunkRegSet.X.BP   = Regs->X.BP;
186   ThunkRegSet.X.BX   = Regs->X.BX;
187   ThunkRegSet.X.DX   = Regs->X.DX;
188   //
189   // Sometimes, ECX is used to pass in 32 bit data. For example, INT 1Ah, AX = B10Dh is
190   // "PCI BIOS v2.0c + Write Configuration DWORD" and ECX has the dword to write.
191   //
192   ThunkRegSet.E.ECX   = Regs->E.ECX;
193   ThunkRegSet.X.AX   = Regs->X.AX;
194   ThunkRegSet.E.DS   = Regs->X.DS;
195   ThunkRegSet.E.ES   = Regs->X.ES;
196 
197   CopyMem (&(ThunkRegSet.E.EFLAGS.UintN), &(Regs->X.Flags), sizeof (Regs->X.Flags));
198 
199   //
200   // Clear the error flag; thunk code may set it. Stack16 should be the high address
201   // Make Statk16 address the low 16 bit must be not zero.
202   //
203   Stack16 = (UINT16 *)((UINT8 *) mThunkContext.RealModeBuffer + mThunkContext.RealModeBufferSize - sizeof (UINT16));
204 
205   //
206   // Save current rate of DXE Timer
207   //
208   Private->Timer->GetTimerPeriod (Private->Timer, &TimerPeriod);
209 
210   //
211   // Disable DXE Timer while executing in real mode
212   //
213   Private->Timer->SetTimerPeriod (Private->Timer, 0);
214 
215   //
216   // Save and disable interrupt of debug timer
217   //
218   InterruptState = SaveAndSetDebugTimerInterrupt (FALSE);
219 
220   //
221   // The call to Legacy16 is a critical section to EFI
222   //
223   OriginalTpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);
224 
225   //
226   // Check to see if there is more than one HW interrupt registers with the CPU AP.
227   // If there is, then ASSERT() since that is not compatible with the CSM because
228   // interupts other than the Timer interrupt that was disabled above can not be
229   // handled properly from real mode.
230   //
231   DEBUG_CODE (
232     UINTN  Vector;
233     UINTN  Count;
234 
235     for (Vector = 0x20, Count = 0; Vector < 0x100; Vector++) {
236       Status = Private->Cpu->RegisterInterruptHandler (Private->Cpu, Vector, LegacyBiosNullInterruptHandler);
237       if (Status == EFI_ALREADY_STARTED) {
238         Count++;
239       }
240       if (Status == EFI_SUCCESS) {
241         Private->Cpu->RegisterInterruptHandler (Private->Cpu, Vector, NULL);
242       }
243     }
244     if (Count >= 2) {
245       DEBUG ((EFI_D_ERROR, "ERROR: More than one HW interrupt active with CSM enabled\n"));
246     }
247     ASSERT (Count < 2);
248   );
249 
250   //
251   // If the Timer AP has enabled the 8254 timer IRQ and the current 8254 timer
252   // period is less than the CSM required rate of 54.9254, then force the 8254
253   // PIT counter to 0, which is the CSM required rate of 54.9254 ms
254   //
255   if (Private->TimerUses8254 && TimerPeriod < 549254) {
256     SetPitCount (0);
257   }
258 
259   if (Stack != NULL && StackSize != 0) {
260     //
261     // Copy Stack to low memory stack
262     //
263     Stack16 -= StackSize / sizeof (UINT16);
264     CopyMem (Stack16, Stack, StackSize);
265   }
266 
267   ThunkRegSet.E.SS   = (UINT16) (((UINTN) Stack16 >> 16) << 12);
268   ThunkRegSet.E.ESP  = (UINT16) (UINTN) Stack16;
269   ThunkRegSet.E.CS   = Segment;
270   ThunkRegSet.E.Eip  = Offset;
271 
272   mThunkContext.RealModeState      = &ThunkRegSet;
273 
274   //
275   // Set Legacy16 state. 0x08, 0x70 is legacy 8259 vector bases.
276   //
277   Status = Private->Legacy8259->SetMode (Private->Legacy8259, Efi8259LegacyMode, NULL, NULL);
278   ASSERT_EFI_ERROR (Status);
279 
280   AsmThunk16 (&mThunkContext);
281 
282   if (Stack != NULL && StackSize != 0) {
283     //
284     // Copy low memory stack to Stack
285     //
286     CopyMem (Stack, Stack16, StackSize);
287   }
288 
289   //
290   // Restore protected mode interrupt state
291   //
292   Status = Private->Legacy8259->SetMode (Private->Legacy8259, Efi8259ProtectedMode, NULL, NULL);
293   ASSERT_EFI_ERROR (Status);
294 
295   mThunkContext.RealModeState = NULL;
296 
297   //
298   // Enable and restore rate of DXE Timer
299   //
300   Private->Timer->SetTimerPeriod (Private->Timer, TimerPeriod);
301 
302   //
303   // End critical section
304   //
305   gBS->RestoreTPL (OriginalTpl);
306 
307   //
308   // OPROM may allocate EBDA range by itself and change EBDA base and EBDA size.
309   // Get the current EBDA base address, and compared with pre-allocate minimum
310   // EBDA base address, if the current EBDA base address is smaller, it indicates
311   // PcdEbdaReservedMemorySize should be adjusted to larger for more OPROMs.
312   //
313   DEBUG_CODE (
314     {
315       UINTN                 EbdaBaseAddress;
316       UINTN                 ReservedEbdaBaseAddress;
317 
318       ACCESS_PAGE0_CODE (
319         EbdaBaseAddress = (*(UINT16 *) (UINTN) 0x40E) << 4;
320         ReservedEbdaBaseAddress = CONVENTIONAL_MEMORY_TOP
321                                   - PcdGet32 (PcdEbdaReservedMemorySize);
322         ASSERT (ReservedEbdaBaseAddress <= EbdaBaseAddress);
323       );
324     }
325   );
326 
327   //
328   // Restore interrupt of debug timer
329   //
330   SaveAndSetDebugTimerInterrupt (InterruptState);
331 
332   Regs->E.EDI      = ThunkRegSet.E.EDI;
333   Regs->E.ESI      = ThunkRegSet.E.ESI;
334   Regs->E.EBP      = ThunkRegSet.E.EBP;
335   Regs->E.EBX      = ThunkRegSet.E.EBX;
336   Regs->E.EDX      = ThunkRegSet.E.EDX;
337   Regs->E.ECX      = ThunkRegSet.E.ECX;
338   Regs->E.EAX      = ThunkRegSet.E.EAX;
339   Regs->X.SS       = ThunkRegSet.E.SS;
340   Regs->X.CS       = ThunkRegSet.E.CS;
341   Regs->X.DS       = ThunkRegSet.E.DS;
342   Regs->X.ES       = ThunkRegSet.E.ES;
343 
344   CopyMem (&(Regs->X.Flags), &(ThunkRegSet.E.EFLAGS.UintN), sizeof (Regs->X.Flags));
345 
346   return (BOOLEAN) (Regs->X.Flags.CF == 1);
347 }
348 
349 /**
350   Allocate memory < 1 MB and copy the thunker code into low memory. Se up
351   all the descriptors.
352 
353   @param  Private                Private context for Legacy BIOS
354 
355   @retval EFI_SUCCESS            Should only pass.
356 
357 **/
358 EFI_STATUS
LegacyBiosInitializeThunk(IN LEGACY_BIOS_INSTANCE * Private)359 LegacyBiosInitializeThunk (
360   IN  LEGACY_BIOS_INSTANCE    *Private
361   )
362 {
363   EFI_STATUS              Status;
364   EFI_PHYSICAL_ADDRESS    MemoryAddress;
365   UINT8                   TimerVector;
366 
367   MemoryAddress   = (EFI_PHYSICAL_ADDRESS) (UINTN) Private->IntThunk;
368 
369   mThunkContext.RealModeBuffer     = (VOID *) (UINTN) (MemoryAddress + ((sizeof (LOW_MEMORY_THUNK) / EFI_PAGE_SIZE) + 1) * EFI_PAGE_SIZE);
370   mThunkContext.RealModeBufferSize = EFI_PAGE_SIZE;
371   mThunkContext.ThunkAttributes    = THUNK_ATTRIBUTE_BIG_REAL_MODE | THUNK_ATTRIBUTE_DISABLE_A20_MASK_INT_15;
372 
373   AsmPrepareThunk16 (&mThunkContext);
374 
375   //
376   // Get the interrupt vector number corresponding to IRQ0 from the 8259 driver
377   //
378   TimerVector = 0;
379   Status = Private->Legacy8259->GetVector (Private->Legacy8259, Efi8259Irq0, &TimerVector);
380   ASSERT_EFI_ERROR (Status);
381 
382   //
383   // Check to see if the Timer AP has hooked the IRQ0 from the 8254 PIT
384   //
385   Status = Private->Cpu->RegisterInterruptHandler (
386                            Private->Cpu,
387                            TimerVector,
388                            LegacyBiosNullInterruptHandler
389                            );
390   if (Status == EFI_SUCCESS) {
391     //
392     // If the Timer AP has not enabled the 8254 timer IRQ, then force the 8254 PIT
393     // counter to 0, which is the CSM required rate of 54.9254 ms
394     //
395     Private->Cpu->RegisterInterruptHandler (
396                     Private->Cpu,
397                     TimerVector,
398                     NULL
399                     );
400     SetPitCount (0);
401 
402     //
403     // Save status that the Timer AP is not using the 8254 PIT
404     //
405     Private->TimerUses8254 = FALSE;
406   } else if (Status == EFI_ALREADY_STARTED) {
407     //
408     // Save status that the Timer AP is using the 8254 PIT
409     //
410     Private->TimerUses8254 = TRUE;
411   } else {
412     //
413     // Unexpected status from CPU AP RegisterInterruptHandler()
414     //
415     ASSERT (FALSE);
416   }
417 
418   return EFI_SUCCESS;
419 }
420