1 /** @file
2   Call into 16-bit BIOS code, Use AsmThunk16 function of BaseLib.
3 
4 Copyright (c) 2006 - 2011, Intel Corporation. All rights reserved.<BR>
5 
6 This program and the accompanying materials
7 are licensed and made available under the terms and conditions
8 of the BSD License which accompanies this distribution.  The
9 full text of the license may be found at
10 http://opensource.org/licenses/bsd-license.php
11 
12 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
13 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
14 
15 **/
16 
17 #include "LegacyBiosInterface.h"
18 
19 THUNK_CONTEXT      mThunkContext;
20 
21 /**
22   Sets the counter value for Timer #0 in a legacy 8254 timer.
23 
24   @param  Count - The 16-bit counter value to program into Timer #0 of the legacy 8254 timer.
25 
26 **/
27 VOID
SetPitCount(IN UINT16 Count)28 SetPitCount (
29   IN UINT16  Count
30   )
31 {
32   IoWrite8 (TIMER_CONTROL_PORT, TIMER0_CONTROL_WORD);
33   IoWrite8 (TIMER0_COUNT_PORT, (UINT8) (Count & 0xFF));
34   IoWrite8 (TIMER0_COUNT_PORT, (UINT8) ((Count>>8) & 0xFF));
35 }
36 
37 /**
38   Thunk to 16-bit real mode and execute a software interrupt with a vector
39   of BiosInt. Regs will contain the 16-bit register context on entry and
40   exit.
41 
42   @param  This    Protocol instance pointer.
43   @param  BiosInt Processor interrupt vector to invoke
44   @param  Regs    Register contexted passed into (and returned) from thunk to
45                   16-bit mode
46 
47   @retval FALSE   Thunk completed, and there were no BIOS errors in the target code.
48                   See Regs for status.
49   @retval TRUE    There was a BIOS erro in the target code.
50 
51 **/
52 BOOLEAN
53 EFIAPI
LegacyBiosInt86(IN EFI_LEGACY_BIOS_PROTOCOL * This,IN UINT8 BiosInt,IN EFI_IA32_REGISTER_SET * Regs)54 LegacyBiosInt86 (
55   IN  EFI_LEGACY_BIOS_PROTOCOL      *This,
56   IN  UINT8                         BiosInt,
57   IN  EFI_IA32_REGISTER_SET         *Regs
58   )
59 {
60   UINT32  *VectorBase;
61 
62   Regs->X.Flags.Reserved1 = 1;
63   Regs->X.Flags.Reserved2 = 0;
64   Regs->X.Flags.Reserved3 = 0;
65   Regs->X.Flags.Reserved4 = 0;
66   Regs->X.Flags.IOPL      = 3;
67   Regs->X.Flags.NT        = 0;
68   Regs->X.Flags.IF        = 0;
69   Regs->X.Flags.TF        = 0;
70   Regs->X.Flags.CF        = 0;
71   //
72   // The base address of legacy interrupt vector table is 0.
73   // We use this base address to get the legacy interrupt handler.
74   //
75   VectorBase              = 0;
76 
77   return InternalLegacyBiosFarCall (
78            This,
79            (UINT16) ((VectorBase)[BiosInt] >> 16),
80            (UINT16) (VectorBase)[BiosInt],
81            Regs,
82            &Regs->X.Flags,
83            sizeof (Regs->X.Flags)
84            );
85 }
86 
87 /**
88   Thunk to 16-bit real mode and call Segment:Offset. Regs will contain the
89   16-bit register context on entry and exit. Arguments can be passed on
90   the Stack argument
91 
92   @param  This                   Protocol instance pointer.
93   @param  Segment                Segemnt of 16-bit mode call
94   @param  Offset                 Offset of 16-bit mdoe call
95   @param  Regs                   Register contexted passed into (and returned) from
96                                  thunk to  16-bit mode
97   @param  Stack                  Caller allocated stack used to pass arguments
98   @param  StackSize              Size of Stack in bytes
99 
100   @retval FALSE                  Thunk completed, and there were no BIOS errors in
101                                  the target code. See Regs for status.
102   @retval TRUE                   There was a BIOS erro in the target code.
103 
104 **/
105 BOOLEAN
106 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)107 LegacyBiosFarCall86 (
108   IN  EFI_LEGACY_BIOS_PROTOCOL        *This,
109   IN  UINT16                          Segment,
110   IN  UINT16                          Offset,
111   IN  EFI_IA32_REGISTER_SET           *Regs,
112   IN  VOID                            *Stack,
113   IN  UINTN                           StackSize
114   )
115 {
116   Regs->X.Flags.Reserved1 = 1;
117   Regs->X.Flags.Reserved2 = 0;
118   Regs->X.Flags.Reserved3 = 0;
119   Regs->X.Flags.Reserved4 = 0;
120   Regs->X.Flags.IOPL      = 3;
121   Regs->X.Flags.NT        = 0;
122   Regs->X.Flags.IF        = 1;
123   Regs->X.Flags.TF        = 0;
124   Regs->X.Flags.CF        = 0;
125 
126   return InternalLegacyBiosFarCall (This, Segment, Offset, Regs, Stack, StackSize);
127 }
128 
129 /**
130   Provide NULL interrupt handler which is used to check
131   if there is more than one HW interrupt registers with the CPU AP.
132 
133   @param  InterruptType - The type of interrupt that occured
134   @param  SystemContext - A pointer to the system context when the interrupt occured
135 
136 **/
137 VOID
138 EFIAPI
LegacyBiosNullInterruptHandler(IN EFI_EXCEPTION_TYPE InterruptType,IN EFI_SYSTEM_CONTEXT SystemContext)139 LegacyBiosNullInterruptHandler (
140   IN EFI_EXCEPTION_TYPE   InterruptType,
141   IN EFI_SYSTEM_CONTEXT   SystemContext
142   )
143 {
144 }
145 
146 /**
147   Thunk to 16-bit real mode and call Segment:Offset. Regs will contain the
148   16-bit register context on entry and exit. Arguments can be passed on
149   the Stack argument
150 
151   @param  This       Protocol instance pointer.
152   @param  Segment    Segemnt of 16-bit mode call
153   @param  Offset     Offset of 16-bit mdoe call
154   @param  Regs       Register contexted passed into (and returned) from thunk to
155                      16-bit mode
156   @param  Stack      Caller allocated stack used to pass arguments
157   @param  StackSize  Size of Stack in bytes
158 
159   @retval FALSE      Thunk completed, and there were no BIOS errors in the target code.
160                      See Regs for status.
161   @retval TRUE       There was a BIOS erro in the target code.
162 
163 **/
164 BOOLEAN
165 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)166 InternalLegacyBiosFarCall (
167   IN  EFI_LEGACY_BIOS_PROTOCOL        *This,
168   IN  UINT16                          Segment,
169   IN  UINT16                          Offset,
170   IN  EFI_IA32_REGISTER_SET           *Regs,
171   IN  VOID                            *Stack,
172   IN  UINTN                           StackSize
173   )
174 {
175   UINTN                 Status;
176   LEGACY_BIOS_INSTANCE  *Private;
177   UINT16                *Stack16;
178   EFI_TPL               OriginalTpl;
179   IA32_REGISTER_SET     ThunkRegSet;
180   BOOLEAN               InterruptState;
181   UINT64                TimerPeriod;
182 
183   Private = LEGACY_BIOS_INSTANCE_FROM_THIS (This);
184 
185   ZeroMem (&ThunkRegSet, sizeof (ThunkRegSet));
186   ThunkRegSet.X.DI   = Regs->X.DI;
187   ThunkRegSet.X.SI   = Regs->X.SI;
188   ThunkRegSet.X.BP   = Regs->X.BP;
189   ThunkRegSet.X.BX   = Regs->X.BX;
190   ThunkRegSet.X.DX   = Regs->X.DX;
191   //
192   // Sometimes, ECX is used to pass in 32 bit data. For example, INT 1Ah, AX = B10Dh is
193   // "PCI BIOS v2.0c + Write Configuration DWORD" and ECX has the dword to write.
194   //
195   ThunkRegSet.E.ECX   = Regs->E.ECX;
196   ThunkRegSet.X.AX   = Regs->X.AX;
197   ThunkRegSet.E.DS   = Regs->X.DS;
198   ThunkRegSet.E.ES   = Regs->X.ES;
199 
200   CopyMem (&(ThunkRegSet.E.EFLAGS.UintN), &(Regs->X.Flags), sizeof (Regs->X.Flags));
201 
202   //
203   // Clear the error flag; thunk code may set it. Stack16 should be the high address
204   // Make Statk16 address the low 16 bit must be not zero.
205   //
206   Stack16 = (UINT16 *)((UINT8 *) mThunkContext.RealModeBuffer + mThunkContext.RealModeBufferSize - sizeof (UINT16));
207 
208   //
209   // Save current rate of DXE Timer
210   //
211   Private->Timer->GetTimerPeriod (Private->Timer, &TimerPeriod);
212 
213   //
214   // Disable DXE Timer while executing in real mode
215   //
216   Private->Timer->SetTimerPeriod (Private->Timer, 0);
217 
218   //
219   // Save and disable interrupt of debug timer
220   //
221   InterruptState = SaveAndSetDebugTimerInterrupt (FALSE);
222 
223   //
224   // The call to Legacy16 is a critical section to EFI
225   //
226   OriginalTpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);
227 
228   //
229   // Check to see if there is more than one HW interrupt registers with the CPU AP.
230   // If there is, then ASSERT() since that is not compatible with the CSM because
231   // interupts other than the Timer interrupt that was disabled above can not be
232   // handled properly from real mode.
233   //
234   DEBUG_CODE (
235     UINTN  Vector;
236     UINTN  Count;
237 
238     for (Vector = 0x20, Count = 0; Vector < 0x100; Vector++) {
239       Status = Private->Cpu->RegisterInterruptHandler (Private->Cpu, Vector, LegacyBiosNullInterruptHandler);
240       if (Status == EFI_ALREADY_STARTED) {
241         Count++;
242       }
243       if (Status == EFI_SUCCESS) {
244         Private->Cpu->RegisterInterruptHandler (Private->Cpu, Vector, NULL);
245       }
246     }
247     if (Count >= 2) {
248       DEBUG ((EFI_D_ERROR, "ERROR: More than one HW interrupt active with CSM enabled\n"));
249     }
250     ASSERT (Count < 2);
251   );
252 
253   //
254   // If the Timer AP has enabled the 8254 timer IRQ and the current 8254 timer
255   // period is less than the CSM required rate of 54.9254, then force the 8254
256   // PIT counter to 0, which is the CSM required rate of 54.9254 ms
257   //
258   if (Private->TimerUses8254 && TimerPeriod < 549254) {
259     SetPitCount (0);
260   }
261 
262   if (Stack != NULL && StackSize != 0) {
263     //
264     // Copy Stack to low memory stack
265     //
266     Stack16 -= StackSize / sizeof (UINT16);
267     CopyMem (Stack16, Stack, StackSize);
268   }
269 
270   ThunkRegSet.E.SS   = (UINT16) (((UINTN) Stack16 >> 16) << 12);
271   ThunkRegSet.E.ESP  = (UINT16) (UINTN) Stack16;
272   ThunkRegSet.E.CS   = Segment;
273   ThunkRegSet.E.Eip  = Offset;
274 
275   mThunkContext.RealModeState      = &ThunkRegSet;
276 
277   //
278   // Set Legacy16 state. 0x08, 0x70 is legacy 8259 vector bases.
279   //
280   Status = Private->Legacy8259->SetMode (Private->Legacy8259, Efi8259LegacyMode, NULL, NULL);
281   ASSERT_EFI_ERROR (Status);
282 
283   AsmThunk16 (&mThunkContext);
284 
285   //
286   // OPROM may allocate EBDA range by itself and change EBDA base and EBDA size.
287   // Get the current EBDA base address, and compared with pre-allocate minimum
288   // EBDA base address, if the current EBDA base address is smaller, it indicates
289   // PcdEbdaReservedMemorySize should be adjusted to larger for more OPROMs.
290   //
291   DEBUG_CODE (
292     {
293       UINTN                 EbdaBaseAddress;
294       UINTN                 ReservedEbdaBaseAddress;
295 
296       EbdaBaseAddress = (*(UINT16 *) (UINTN) 0x40E) << 4;
297       ReservedEbdaBaseAddress = CONVENTIONAL_MEMORY_TOP - PcdGet32 (PcdEbdaReservedMemorySize);
298       ASSERT (ReservedEbdaBaseAddress <= EbdaBaseAddress);
299     }
300   );
301 
302   if (Stack != NULL && StackSize != 0) {
303     //
304     // Copy low memory stack to Stack
305     //
306     CopyMem (Stack, Stack16, StackSize);
307   }
308 
309   //
310   // Restore protected mode interrupt state
311   //
312   Status = Private->Legacy8259->SetMode (Private->Legacy8259, Efi8259ProtectedMode, NULL, NULL);
313   ASSERT_EFI_ERROR (Status);
314 
315   mThunkContext.RealModeState = NULL;
316 
317   //
318   // Enable and restore rate of DXE Timer
319   //
320   Private->Timer->SetTimerPeriod (Private->Timer, TimerPeriod);
321 
322   //
323   // End critical section
324   //
325   gBS->RestoreTPL (OriginalTpl);
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