]> git.proxmox.com Git - mirror_edk2.git/blobdiff - OvmfPkg/Csm/LegacyBiosDxe/Thunk.c
OvmfPkg: Copy the required CSM components from framework packages
[mirror_edk2.git] / OvmfPkg / Csm / LegacyBiosDxe / Thunk.c
diff --git a/OvmfPkg/Csm/LegacyBiosDxe/Thunk.c b/OvmfPkg/Csm/LegacyBiosDxe/Thunk.c
new file mode 100644 (file)
index 0000000..a4985ed
--- /dev/null
@@ -0,0 +1,419 @@
+/** @file\r
+  Call into 16-bit BIOS code, Use AsmThunk16 function of BaseLib.\r
+\r
+Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR>\r
+\r
+SPDX-License-Identifier: BSD-2-Clause-Patent\r
+\r
+**/\r
+\r
+#include "LegacyBiosInterface.h"\r
+\r
+THUNK_CONTEXT      mThunkContext;\r
+\r
+/**\r
+  Sets the counter value for Timer #0 in a legacy 8254 timer.\r
+\r
+  @param  Count - The 16-bit counter value to program into Timer #0 of the legacy 8254 timer.\r
+\r
+**/\r
+VOID\r
+SetPitCount (\r
+  IN UINT16  Count\r
+  )\r
+{\r
+  IoWrite8 (TIMER_CONTROL_PORT, TIMER0_CONTROL_WORD);\r
+  IoWrite8 (TIMER0_COUNT_PORT, (UINT8) (Count & 0xFF));\r
+  IoWrite8 (TIMER0_COUNT_PORT, (UINT8) ((Count>>8) & 0xFF));\r
+}\r
+\r
+/**\r
+  Thunk to 16-bit real mode and execute a software interrupt with a vector\r
+  of BiosInt. Regs will contain the 16-bit register context on entry and\r
+  exit.\r
+\r
+  @param  This    Protocol instance pointer.\r
+  @param  BiosInt Processor interrupt vector to invoke\r
+  @param  Regs    Register contexted passed into (and returned) from thunk to\r
+                  16-bit mode\r
+\r
+  @retval FALSE   Thunk completed, and there were no BIOS errors in the target code.\r
+                  See Regs for status.\r
+  @retval TRUE    There was a BIOS erro in the target code.\r
+\r
+**/\r
+BOOLEAN\r
+EFIAPI\r
+LegacyBiosInt86 (\r
+  IN  EFI_LEGACY_BIOS_PROTOCOL      *This,\r
+  IN  UINT8                         BiosInt,\r
+  IN  EFI_IA32_REGISTER_SET         *Regs\r
+  )\r
+{\r
+  UINT16                Segment;\r
+  UINT16                Offset;\r
+\r
+  Regs->X.Flags.Reserved1 = 1;\r
+  Regs->X.Flags.Reserved2 = 0;\r
+  Regs->X.Flags.Reserved3 = 0;\r
+  Regs->X.Flags.Reserved4 = 0;\r
+  Regs->X.Flags.IOPL      = 3;\r
+  Regs->X.Flags.NT        = 0;\r
+  Regs->X.Flags.IF        = 0;\r
+  Regs->X.Flags.TF        = 0;\r
+  Regs->X.Flags.CF        = 0;\r
+  //\r
+  // The base address of legacy interrupt vector table is 0.\r
+  // We use this base address to get the legacy interrupt handler.\r
+  //\r
+  ACCESS_PAGE0_CODE (\r
+    Segment               = (UINT16)(((UINT32 *)0)[BiosInt] >> 16);\r
+    Offset                = (UINT16)((UINT32 *)0)[BiosInt];\r
+  );\r
+\r
+  return InternalLegacyBiosFarCall (\r
+           This,\r
+           Segment,\r
+           Offset,\r
+           Regs,\r
+           &Regs->X.Flags,\r
+           sizeof (Regs->X.Flags)\r
+           );\r
+}\r
+\r
+/**\r
+  Thunk to 16-bit real mode and call Segment:Offset. Regs will contain the\r
+  16-bit register context on entry and exit. Arguments can be passed on\r
+  the Stack argument\r
+\r
+  @param  This                   Protocol instance pointer.\r
+  @param  Segment                Segemnt of 16-bit mode call\r
+  @param  Offset                 Offset of 16-bit mdoe call\r
+  @param  Regs                   Register contexted passed into (and returned) from\r
+                                 thunk to  16-bit mode\r
+  @param  Stack                  Caller allocated stack used to pass arguments\r
+  @param  StackSize              Size of Stack in bytes\r
+\r
+  @retval FALSE                  Thunk completed, and there were no BIOS errors in\r
+                                 the target code. See Regs for status.\r
+  @retval TRUE                   There was a BIOS erro in the target code.\r
+\r
+**/\r
+BOOLEAN\r
+EFIAPI\r
+LegacyBiosFarCall86 (\r
+  IN  EFI_LEGACY_BIOS_PROTOCOL        *This,\r
+  IN  UINT16                          Segment,\r
+  IN  UINT16                          Offset,\r
+  IN  EFI_IA32_REGISTER_SET           *Regs,\r
+  IN  VOID                            *Stack,\r
+  IN  UINTN                           StackSize\r
+  )\r
+{\r
+  Regs->X.Flags.Reserved1 = 1;\r
+  Regs->X.Flags.Reserved2 = 0;\r
+  Regs->X.Flags.Reserved3 = 0;\r
+  Regs->X.Flags.Reserved4 = 0;\r
+  Regs->X.Flags.IOPL      = 3;\r
+  Regs->X.Flags.NT        = 0;\r
+  Regs->X.Flags.IF        = 1;\r
+  Regs->X.Flags.TF        = 0;\r
+  Regs->X.Flags.CF        = 0;\r
+\r
+  return InternalLegacyBiosFarCall (This, Segment, Offset, Regs, Stack, StackSize);\r
+}\r
+\r
+/**\r
+  Provide NULL interrupt handler which is used to check\r
+  if there is more than one HW interrupt registers with the CPU AP.\r
+\r
+  @param  InterruptType - The type of interrupt that occured\r
+  @param  SystemContext - A pointer to the system context when the interrupt occured\r
+\r
+**/\r
+VOID\r
+EFIAPI\r
+LegacyBiosNullInterruptHandler (\r
+  IN EFI_EXCEPTION_TYPE   InterruptType,\r
+  IN EFI_SYSTEM_CONTEXT   SystemContext\r
+  )\r
+{\r
+}\r
+\r
+/**\r
+  Thunk to 16-bit real mode and call Segment:Offset. Regs will contain the\r
+  16-bit register context on entry and exit. Arguments can be passed on\r
+  the Stack argument\r
+\r
+  @param  This       Protocol instance pointer.\r
+  @param  Segment    Segemnt of 16-bit mode call\r
+  @param  Offset     Offset of 16-bit mdoe call\r
+  @param  Regs       Register contexted passed into (and returned) from thunk to\r
+                     16-bit mode\r
+  @param  Stack      Caller allocated stack used to pass arguments\r
+  @param  StackSize  Size of Stack in bytes\r
+\r
+  @retval FALSE      Thunk completed, and there were no BIOS errors in the target code.\r
+                     See Regs for status.\r
+  @retval TRUE       There was a BIOS erro in the target code.\r
+\r
+**/\r
+BOOLEAN\r
+EFIAPI\r
+InternalLegacyBiosFarCall (\r
+  IN  EFI_LEGACY_BIOS_PROTOCOL        *This,\r
+  IN  UINT16                          Segment,\r
+  IN  UINT16                          Offset,\r
+  IN  EFI_IA32_REGISTER_SET           *Regs,\r
+  IN  VOID                            *Stack,\r
+  IN  UINTN                           StackSize\r
+  )\r
+{\r
+  UINTN                 Status;\r
+  LEGACY_BIOS_INSTANCE  *Private;\r
+  UINT16                *Stack16;\r
+  EFI_TPL               OriginalTpl;\r
+  IA32_REGISTER_SET     ThunkRegSet;\r
+  BOOLEAN               InterruptState;\r
+  UINT64                TimerPeriod;\r
+\r
+  Private = LEGACY_BIOS_INSTANCE_FROM_THIS (This);\r
+\r
+  ZeroMem (&ThunkRegSet, sizeof (ThunkRegSet));\r
+  ThunkRegSet.X.DI   = Regs->X.DI;\r
+  ThunkRegSet.X.SI   = Regs->X.SI;\r
+  ThunkRegSet.X.BP   = Regs->X.BP;\r
+  ThunkRegSet.X.BX   = Regs->X.BX;\r
+  ThunkRegSet.X.DX   = Regs->X.DX;\r
+  //\r
+  // Sometimes, ECX is used to pass in 32 bit data. For example, INT 1Ah, AX = B10Dh is\r
+  // "PCI BIOS v2.0c + Write Configuration DWORD" and ECX has the dword to write.\r
+  //\r
+  ThunkRegSet.E.ECX   = Regs->E.ECX;\r
+  ThunkRegSet.X.AX   = Regs->X.AX;\r
+  ThunkRegSet.E.DS   = Regs->X.DS;\r
+  ThunkRegSet.E.ES   = Regs->X.ES;\r
+\r
+  CopyMem (&(ThunkRegSet.E.EFLAGS.UintN), &(Regs->X.Flags), sizeof (Regs->X.Flags));\r
+\r
+  //\r
+  // Clear the error flag; thunk code may set it. Stack16 should be the high address\r
+  // Make Statk16 address the low 16 bit must be not zero.\r
+  //\r
+  Stack16 = (UINT16 *)((UINT8 *) mThunkContext.RealModeBuffer + mThunkContext.RealModeBufferSize - sizeof (UINT16));\r
+\r
+  //\r
+  // Save current rate of DXE Timer\r
+  //\r
+  Private->Timer->GetTimerPeriod (Private->Timer, &TimerPeriod);\r
+\r
+  //\r
+  // Disable DXE Timer while executing in real mode\r
+  //\r
+  Private->Timer->SetTimerPeriod (Private->Timer, 0);\r
+\r
+  //\r
+  // Save and disable interrupt of debug timer\r
+  //\r
+  InterruptState = SaveAndSetDebugTimerInterrupt (FALSE);\r
+\r
+  //\r
+  // The call to Legacy16 is a critical section to EFI\r
+  //\r
+  OriginalTpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);\r
+\r
+  //\r
+  // Check to see if there is more than one HW interrupt registers with the CPU AP.\r
+  // If there is, then ASSERT() since that is not compatible with the CSM because\r
+  // interupts other than the Timer interrupt that was disabled above can not be\r
+  // handled properly from real mode.\r
+  //\r
+  DEBUG_CODE (\r
+    UINTN  Vector;\r
+    UINTN  Count;\r
+\r
+    for (Vector = 0x20, Count = 0; Vector < 0x100; Vector++) {\r
+      Status = Private->Cpu->RegisterInterruptHandler (Private->Cpu, Vector, LegacyBiosNullInterruptHandler);\r
+      if (Status == EFI_ALREADY_STARTED) {\r
+        Count++;\r
+      }\r
+      if (Status == EFI_SUCCESS) {\r
+        Private->Cpu->RegisterInterruptHandler (Private->Cpu, Vector, NULL);\r
+      }\r
+    }\r
+    if (Count >= 2) {\r
+      DEBUG ((EFI_D_ERROR, "ERROR: More than one HW interrupt active with CSM enabled\n"));\r
+    }\r
+    ASSERT (Count < 2);\r
+  );\r
+\r
+  //\r
+  // If the Timer AP has enabled the 8254 timer IRQ and the current 8254 timer\r
+  // period is less than the CSM required rate of 54.9254, then force the 8254\r
+  // PIT counter to 0, which is the CSM required rate of 54.9254 ms\r
+  //\r
+  if (Private->TimerUses8254 && TimerPeriod < 549254) {\r
+    SetPitCount (0);\r
+  }\r
+\r
+  if (Stack != NULL && StackSize != 0) {\r
+    //\r
+    // Copy Stack to low memory stack\r
+    //\r
+    Stack16 -= StackSize / sizeof (UINT16);\r
+    CopyMem (Stack16, Stack, StackSize);\r
+  }\r
+\r
+  ThunkRegSet.E.SS   = (UINT16) (((UINTN) Stack16 >> 16) << 12);\r
+  ThunkRegSet.E.ESP  = (UINT16) (UINTN) Stack16;\r
+  ThunkRegSet.E.CS   = Segment;\r
+  ThunkRegSet.E.Eip  = Offset;\r
+\r
+  mThunkContext.RealModeState      = &ThunkRegSet;\r
+\r
+  //\r
+  // Set Legacy16 state. 0x08, 0x70 is legacy 8259 vector bases.\r
+  //\r
+  Status = Private->Legacy8259->SetMode (Private->Legacy8259, Efi8259LegacyMode, NULL, NULL);\r
+  ASSERT_EFI_ERROR (Status);\r
+\r
+  AsmThunk16 (&mThunkContext);\r
+\r
+  if (Stack != NULL && StackSize != 0) {\r
+    //\r
+    // Copy low memory stack to Stack\r
+    //\r
+    CopyMem (Stack, Stack16, StackSize);\r
+  }\r
+\r
+  //\r
+  // Restore protected mode interrupt state\r
+  //\r
+  Status = Private->Legacy8259->SetMode (Private->Legacy8259, Efi8259ProtectedMode, NULL, NULL);\r
+  ASSERT_EFI_ERROR (Status);\r
+\r
+  mThunkContext.RealModeState = NULL;\r
+\r
+  //\r
+  // Enable and restore rate of DXE Timer\r
+  //\r
+  Private->Timer->SetTimerPeriod (Private->Timer, TimerPeriod);\r
+\r
+  //\r
+  // End critical section\r
+  //\r
+  gBS->RestoreTPL (OriginalTpl);\r
+\r
+  //\r
+  // OPROM may allocate EBDA range by itself and change EBDA base and EBDA size.\r
+  // Get the current EBDA base address, and compared with pre-allocate minimum\r
+  // EBDA base address, if the current EBDA base address is smaller, it indicates\r
+  // PcdEbdaReservedMemorySize should be adjusted to larger for more OPROMs.\r
+  //\r
+  DEBUG_CODE (\r
+    {\r
+      UINTN                 EbdaBaseAddress;\r
+      UINTN                 ReservedEbdaBaseAddress;\r
+\r
+      ACCESS_PAGE0_CODE (\r
+        EbdaBaseAddress = (*(UINT16 *) (UINTN) 0x40E) << 4;\r
+        ReservedEbdaBaseAddress = CONVENTIONAL_MEMORY_TOP\r
+                                  - PcdGet32 (PcdEbdaReservedMemorySize);\r
+        ASSERT (ReservedEbdaBaseAddress <= EbdaBaseAddress);\r
+      );\r
+    }\r
+  );\r
+\r
+  //\r
+  // Restore interrupt of debug timer\r
+  //\r
+  SaveAndSetDebugTimerInterrupt (InterruptState);\r
+\r
+  Regs->E.EDI      = ThunkRegSet.E.EDI;\r
+  Regs->E.ESI      = ThunkRegSet.E.ESI;\r
+  Regs->E.EBP      = ThunkRegSet.E.EBP;\r
+  Regs->E.EBX      = ThunkRegSet.E.EBX;\r
+  Regs->E.EDX      = ThunkRegSet.E.EDX;\r
+  Regs->E.ECX      = ThunkRegSet.E.ECX;\r
+  Regs->E.EAX      = ThunkRegSet.E.EAX;\r
+  Regs->X.SS       = ThunkRegSet.E.SS;\r
+  Regs->X.CS       = ThunkRegSet.E.CS;\r
+  Regs->X.DS       = ThunkRegSet.E.DS;\r
+  Regs->X.ES       = ThunkRegSet.E.ES;\r
+\r
+  CopyMem (&(Regs->X.Flags), &(ThunkRegSet.E.EFLAGS.UintN), sizeof (Regs->X.Flags));\r
+\r
+  return (BOOLEAN) (Regs->X.Flags.CF == 1);\r
+}\r
+\r
+/**\r
+  Allocate memory < 1 MB and copy the thunker code into low memory. Se up\r
+  all the descriptors.\r
+\r
+  @param  Private                Private context for Legacy BIOS\r
+\r
+  @retval EFI_SUCCESS            Should only pass.\r
+\r
+**/\r
+EFI_STATUS\r
+LegacyBiosInitializeThunk (\r
+  IN  LEGACY_BIOS_INSTANCE    *Private\r
+  )\r
+{\r
+  EFI_STATUS              Status;\r
+  EFI_PHYSICAL_ADDRESS    MemoryAddress;\r
+  UINT8                   TimerVector;\r
+\r
+  MemoryAddress   = (EFI_PHYSICAL_ADDRESS) (UINTN) Private->IntThunk;\r
+\r
+  mThunkContext.RealModeBuffer     = (VOID *) (UINTN) (MemoryAddress + ((sizeof (LOW_MEMORY_THUNK) / EFI_PAGE_SIZE) + 1) * EFI_PAGE_SIZE);\r
+  mThunkContext.RealModeBufferSize = EFI_PAGE_SIZE;\r
+  mThunkContext.ThunkAttributes    = THUNK_ATTRIBUTE_BIG_REAL_MODE | THUNK_ATTRIBUTE_DISABLE_A20_MASK_INT_15;\r
+\r
+  AsmPrepareThunk16 (&mThunkContext);\r
+\r
+  //\r
+  // Get the interrupt vector number corresponding to IRQ0 from the 8259 driver\r
+  //\r
+  TimerVector = 0;\r
+  Status = Private->Legacy8259->GetVector (Private->Legacy8259, Efi8259Irq0, &TimerVector);\r
+  ASSERT_EFI_ERROR (Status);\r
+\r
+  //\r
+  // Check to see if the Timer AP has hooked the IRQ0 from the 8254 PIT\r
+  //\r
+  Status = Private->Cpu->RegisterInterruptHandler (\r
+                           Private->Cpu,\r
+                           TimerVector,\r
+                           LegacyBiosNullInterruptHandler\r
+                           );\r
+  if (Status == EFI_SUCCESS) {\r
+    //\r
+    // If the Timer AP has not enabled the 8254 timer IRQ, then force the 8254 PIT\r
+    // counter to 0, which is the CSM required rate of 54.9254 ms\r
+    //\r
+    Private->Cpu->RegisterInterruptHandler (\r
+                    Private->Cpu,\r
+                    TimerVector,\r
+                    NULL\r
+                    );\r
+    SetPitCount (0);\r
+\r
+    //\r
+    // Save status that the Timer AP is not using the 8254 PIT\r
+    //\r
+    Private->TimerUses8254 = FALSE;\r
+  } else if (Status == EFI_ALREADY_STARTED) {\r
+    //\r
+    // Save status that the Timer AP is using the 8254 PIT\r
+    //\r
+    Private->TimerUses8254 = TRUE;\r
+  } else {\r
+    //\r
+    // Unexpected status from CPU AP RegisterInterruptHandler()\r
+    //\r
+    ASSERT (FALSE);\r
+  }\r
+\r
+  return EFI_SUCCESS;\r
+}\r