/** @file | |
Call into 16-bit BIOS code | |
BugBug: Thunker does A20 gate. Can we get rid of this code or | |
put it into Legacy16 code. | |
Copyright (c) 1999 - 2014, Intel Corporation. All rights reserved.<BR> | |
This program and the accompanying materials | |
are licensed and made available under the terms and conditions | |
of the BSD License which accompanies this distribution. The | |
full text of the license may be found at | |
http://opensource.org/licenses/bsd-license.php | |
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
**/ | |
#include "LegacyBiosInterface.h" | |
#include "IpfThunk.h" | |
/** | |
Gets the current flat GDT and IDT descriptors and store them in | |
Private->IntThunk. These values are used by the Thunk code. | |
This method must be called before every thunk in order to assure | |
that the correct GDT and IDT are restored after the thunk. | |
@param Private Private context for Legacy BIOS | |
@retval EFI_SUCCESS Should only pass. | |
**/ | |
EFI_STATUS | |
LegacyBiosGetFlatDescs ( | |
IN LEGACY_BIOS_INSTANCE *Private | |
) | |
{ | |
return EFI_SUCCESS; | |
} | |
/** | |
BIOS interrupt call function. | |
@param BiosInt Int number of BIOS call | |
@param Segment Segment number | |
@param Offset Offset in segment | |
@param Regs IA32 Register set. | |
@param Stack Base address of stack | |
@param StackSize Size of stack | |
@retval EFI_SUCCESS BIOS interrupt call succeeds. | |
**/ | |
EFI_STATUS | |
BiosIntCall ( | |
IN UINT16 BiosInt, | |
IN UINT16 Segment, | |
IN UINT16 Offset, | |
IN EFI_IA32_REGISTER_SET *Regs, | |
IN VOID *Stack, | |
IN UINTN StackSize | |
) | |
{ | |
IPF_DWORD_REGS DwordRegs; | |
UINT64 IntTypeVariable; | |
IntTypeVariable = 0x8000000000000000; | |
IntTypeVariable |= (UINT64)BiosInt; | |
DwordRegs.Cs = Segment; | |
DwordRegs.Eip = Offset; | |
DwordRegs.Ds = Regs->X.DS; | |
DwordRegs.Es = Regs->X.ES; | |
DwordRegs.Fs = Regs->X.ES; | |
DwordRegs.Gs = Regs->X.ES; | |
DwordRegs.Ss = 0xFFFF; | |
DwordRegs.Eax = Regs->X.AX; | |
DwordRegs.Ebx = Regs->X.BX; | |
// | |
// Sometimes, ECX is used to pass in 32 bit data. For example, INT 1Ah, AX = B10Dh is | |
// "PCI BIOS v2.0c + Write Configuration DWORD" and ECX has the dword to write. | |
// | |
DwordRegs.Ecx = Regs->E.ECX; | |
DwordRegs.Edx = Regs->X.DX; | |
DwordRegs.Ebp = Regs->X.BP; | |
DwordRegs.Eflag = *((UINT16 *) &Regs->X.Flags); | |
DwordRegs.Edi = Regs->X.DI; | |
DwordRegs.Esi = Regs->X.SI; | |
DwordRegs.Esp = 0xFFFFFFFF; | |
EfiIaEntryPoint (IntTypeVariable, &DwordRegs, ((UINTN) Stack + StackSize), StackSize); | |
Regs->X.CS = DwordRegs.Cs; | |
Regs->X.DS = (UINT16) DwordRegs.Ds; | |
Regs->X.SS = (UINT16) DwordRegs.Ss; | |
Regs->E.EAX = DwordRegs.Eax; | |
Regs->E.EBX = DwordRegs.Ebx; | |
Regs->E.ECX = DwordRegs.Ecx; | |
Regs->E.EDX = DwordRegs.Edx; | |
Regs->E.EBP = DwordRegs.Ebp; | |
CopyMem (&Regs->X.Flags, &DwordRegs.Eflag, sizeof (EFI_FLAGS_REG)); | |
Regs->E.EDI = DwordRegs.Edi; | |
Regs->E.ESI = DwordRegs.Esi; | |
return EFI_SUCCESS; | |
} | |
/** | |
Template of real mode code. | |
@param CodeStart Start address of code. | |
@param CodeEnd End address of code | |
@param ReverseThunkStart Start of reverse thunk. | |
@param IntThunk Low memory thunk. | |
**/ | |
VOID | |
RealModeTemplate ( | |
OUT UINTN *CodeStart, | |
OUT UINTN *CodeEnd, | |
OUT UINTN *ReverseThunkStart, | |
LOW_MEMORY_THUNK *IntThunk | |
) | |
{ | |
SAL_RETURN_REGS SalStatus; | |
SalStatus = EsalGetReverseThunkAddress (); | |
*CodeStart = SalStatus.r9; | |
*CodeEnd = SalStatus.r10; | |
*ReverseThunkStart = SalStatus.r11; | |
} | |
/** | |
Allocate memory < 1 MB and copy the thunker code into low memory. Se up | |
all the descriptors. | |
@param Private Private context for Legacy BIOS | |
@retval EFI_SUCCESS Should only pass. | |
**/ | |
EFI_STATUS | |
LegacyBiosInitializeThunk ( | |
IN LEGACY_BIOS_INSTANCE *Private | |
) | |
{ | |
GDT32 *CodeGdt; | |
GDT32 *DataGdt; | |
UINTN CodeStart; | |
UINTN CodeEnd; | |
UINTN ReverseThunkStart; | |
UINT32 Base; | |
LOW_MEMORY_THUNK *IntThunk; | |
UINTN TempData; | |
ASSERT (Private); | |
IntThunk = Private->IntThunk; | |
// | |
// Clear the reserved descriptor | |
// | |
ZeroMem (&(IntThunk->RealModeGdt[0]), sizeof (GDT32)); | |
// | |
// Setup a descriptor for real-mode code | |
// | |
CodeGdt = &(IntThunk->RealModeGdt[1]); | |
// | |
// Fill in the descriptor with our real-mode segment value | |
// | |
CodeGdt->Type = 0xA; | |
// | |
// code/read | |
// | |
CodeGdt->System = 1; | |
CodeGdt->Dpl = 0; | |
CodeGdt->Present = 1; | |
CodeGdt->Software = 0; | |
CodeGdt->Reserved = 0; | |
CodeGdt->DefaultSize = 0; | |
// | |
// 16 bit operands | |
// | |
CodeGdt->Granularity = 0; | |
CodeGdt->LimitHi = 0; | |
CodeGdt->LimitLo = 0xffff; | |
Base = (*((UINT32 *) &IntThunk->Code)); | |
CodeGdt->BaseHi = (Base >> 24) & 0xFF; | |
CodeGdt->BaseMid = (Base >> 16) & 0xFF; | |
CodeGdt->BaseLo = Base & 0xFFFF; | |
// | |
// Setup a descriptor for read-mode data | |
// | |
DataGdt = &(IntThunk->RealModeGdt[2]); | |
CopyMem (DataGdt, CodeGdt, sizeof (GDT32)); | |
DataGdt->Type = 0x2; | |
// | |
// read/write data | |
// | |
DataGdt->BaseHi = 0x0; | |
// | |
// Base = 0 | |
// | |
DataGdt->BaseMid = 0x0; | |
// | |
DataGdt->BaseLo = 0x0; | |
// | |
DataGdt->LimitHi = 0x0F; | |
// | |
// Limit = 4Gb | |
// | |
DataGdt->LimitLo = 0xFFFF; | |
// | |
DataGdt->Granularity = 0x1; | |
// | |
// | |
// Compute selector value | |
// | |
IntThunk->RealModeGdtDesc.Limit = (UINT16) (sizeof (IntThunk->RealModeGdt) - 1); | |
CopyMem (&IntThunk->RealModeGdtDesc.Base, (UINT32 *) &IntThunk->RealModeGdt, sizeof (UINT32)); | |
// | |
// IntThunk->RealModeGdtDesc.Base = *((UINT32*) &IntThunk->RealModeGdt); | |
// | |
IntThunk->RealModeIdtDesc.Limit = 0xFFFF; | |
IntThunk->RealModeIdtDesc.Base = 0; | |
IntThunk->LowCodeSelector = (UINT32) ((UINTN) CodeGdt - IntThunk->RealModeGdtDesc.Base); | |
IntThunk->LowDataSelector = (UINT32) ((UINTN) DataGdt - IntThunk->RealModeGdtDesc.Base); | |
// | |
// Initialize low real-mode code thunk | |
// | |
RealModeTemplate (&CodeStart, &CodeEnd, &ReverseThunkStart, IntThunk); | |
TempData = (UINTN) &(IntThunk->Code); | |
IntThunk->LowReverseThunkStart = ((UINT32) TempData + (UINT32) (ReverseThunkStart - CodeStart)); | |
EsalSetSalDataArea (TempData, (UINTN) IntThunk); | |
CopyMem (IntThunk->Code, (VOID *) CodeStart, CodeEnd - CodeStart); | |
IntThunk->EfiToLegacy16InitTable.ReverseThunkCallSegment = EFI_SEGMENT (*((UINT32 *) &IntThunk->LowReverseThunkStart)); | |
IntThunk->EfiToLegacy16InitTable.ReverseThunkCallOffset = EFI_OFFSET (*((UINT32 *) &IntThunk->LowReverseThunkStart)); | |
return EFI_SUCCESS; | |
} | |
/** | |
Thunk to 16-bit real mode and execute a software interrupt with a vector | |
of BiosInt. Regs will contain the 16-bit register context on entry and | |
exit. | |
@param This Protocol instance pointer. | |
@param BiosInt Processor interrupt vector to invoke | |
@param Regs Register contexted passed into (and returned) from | |
thunk to 16-bit mode | |
@retval FALSE Thunk completed, and there were no BIOS errors in the | |
target code. See Regs for status. | |
@retval TRUE There was a BIOS erro in the target code. | |
**/ | |
BOOLEAN | |
EFIAPI | |
LegacyBiosInt86 ( | |
IN EFI_LEGACY_BIOS_PROTOCOL *This, | |
IN UINT8 BiosInt, | |
IN EFI_IA32_REGISTER_SET *Regs | |
) | |
{ | |
EFI_STATUS Status; | |
LEGACY_BIOS_INSTANCE *Private; | |
LOW_MEMORY_THUNK *IntThunk; | |
UINT16 *Stack16; | |
EFI_TPL OriginalTpl; | |
UINTN IaSegment; | |
UINTN IaOffset; | |
UINTN *Address; | |
UINTN TempData; | |
Private = LEGACY_BIOS_INSTANCE_FROM_THIS (This); | |
IntThunk = Private->IntThunk; | |
// | |
// Get the current flat GDT, IDT, and SS and store them in Private->IntThunk. | |
// | |
Status = LegacyBiosGetFlatDescs (Private); | |
ASSERT_EFI_ERROR (Status); | |
Regs->X.Flags.Reserved1 = 1; | |
Regs->X.Flags.Reserved2 = 0; | |
Regs->X.Flags.Reserved3 = 0; | |
Regs->X.Flags.Reserved4 = 0; | |
Regs->X.Flags.IOPL = 3; | |
Regs->X.Flags.NT = 0; | |
Regs->X.Flags.IF = 1; | |
Regs->X.Flags.TF = 0; | |
Regs->X.Flags.CF = 0; | |
// | |
// Clear the error flag; thunk code may set it. | |
// | |
Stack16 = (UINT16 *) (IntThunk->Stack + LOW_STACK_SIZE); | |
// | |
// Copy regs to low memory stack | |
// | |
Stack16 -= sizeof (EFI_IA32_REGISTER_SET) / sizeof (UINT16); | |
CopyMem (Stack16, Regs, sizeof (EFI_IA32_REGISTER_SET)); | |
// | |
// Provide low stack esp | |
// | |
TempData = ((UINTN) Stack16) - ((UINTN) IntThunk); | |
IntThunk->LowStack = *((UINT32 *) &TempData); | |
// | |
// Stack for reverse thunk flat mode. | |
// It must point to top of stack (end of stack space). | |
// | |
TempData = ((UINTN) IntThunk->RevThunkStack) + LOW_STACK_SIZE; | |
IntThunk->RevFlatStack = *((UINT32 *) &TempData); | |
// | |
// The call to Legacy16 is a critical section to EFI | |
// | |
OriginalTpl = gBS->RaiseTPL (TPL_HIGH_LEVEL); | |
// | |
// Set Legacy16 state. 0x08, 0x70 is legacy 8259 vector bases. | |
// | |
Status = Private->Legacy8259->SetMode (Private->Legacy8259, Efi8259LegacyMode, NULL, NULL); | |
ASSERT_EFI_ERROR (Status); | |
// | |
// Call the real mode thunk code | |
// | |
TempData = BiosInt * 4; | |
Address = (UINTN *) TempData; | |
IaOffset = 0xFFFF & (*Address); | |
IaSegment = 0xFFFF & ((*Address) >> 16); | |
Status = BiosIntCall ( | |
BiosInt, | |
(UINT16) IaSegment, | |
(UINT16) IaOffset, | |
(EFI_IA32_REGISTER_SET *) Stack16, | |
IntThunk, | |
IntThunk->LowStack | |
); | |
// | |
// Check for errors with the thunk | |
// | |
switch (Status) { | |
case THUNK_OK: | |
break; | |
case THUNK_ERR_A20_UNSUP: | |
case THUNK_ERR_A20_FAILED: | |
default: | |
// | |
// For all errors, set EFLAGS.CF (used by legacy BIOS to indicate error). | |
// | |
Regs->X.Flags.CF = 1; | |
break; | |
} | |
Status = Private->Legacy8259->SetMode (Private->Legacy8259, Efi8259ProtectedMode, NULL, NULL); | |
ASSERT_EFI_ERROR (Status); | |
// | |
// End critical section | |
// | |
gBS->RestoreTPL (OriginalTpl); | |
// | |
// Return the resulting registers | |
// | |
CopyMem (Regs, Stack16, sizeof (EFI_IA32_REGISTER_SET)); | |
return (BOOLEAN) (Regs->X.Flags.CF != 0); | |
} | |
/** | |
Thunk to 16-bit real mode and call Segment:Offset. Regs will contain the | |
16-bit register context on entry and exit. Arguments can be passed on | |
the Stack argument | |
@param This Protocol instance pointer. | |
@param Segment Segemnt of 16-bit mode call | |
@param Offset Offset of 16-bit mdoe call | |
@param Regs Register contexted passed into (and returned) from | |
thunk to 16-bit mode | |
@param Stack Caller allocated stack used to pass arguments | |
@param StackSize Size of Stack in bytes | |
@retval FALSE Thunk completed, and there were no BIOS errors in the | |
target code. See Regs for status. | |
@retval TRUE There was a BIOS erro in the target code. | |
**/ | |
BOOLEAN | |
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 | |
) | |
{ | |
EFI_STATUS Status; | |
LEGACY_BIOS_INSTANCE *Private; | |
LOW_MEMORY_THUNK *IntThunk; | |
UINT16 *Stack16; | |
EFI_TPL OriginalTpl; | |
UINTN IaSegment; | |
UINTN IaOffset; | |
UINTN TempData; | |
Private = LEGACY_BIOS_INSTANCE_FROM_THIS (This); | |
IntThunk = Private->IntThunk; | |
IaSegment = Segment; | |
IaOffset = Offset; | |
// | |
// Get the current flat GDT and IDT and store them in Private->IntThunk. | |
// | |
Status = LegacyBiosGetFlatDescs (Private); | |
ASSERT_EFI_ERROR (Status); | |
Regs->X.Flags.Reserved1 = 1; | |
Regs->X.Flags.Reserved2 = 0; | |
Regs->X.Flags.Reserved3 = 0; | |
Regs->X.Flags.Reserved4 = 0; | |
Regs->X.Flags.IOPL = 3; | |
Regs->X.Flags.NT = 0; | |
Regs->X.Flags.IF = 1; | |
Regs->X.Flags.TF = 0; | |
Regs->X.Flags.CF = 0; | |
// | |
// Clear the error flag; thunk code may set it. | |
// | |
Stack16 = (UINT16 *) (IntThunk->Stack + LOW_STACK_SIZE); | |
if (Stack != NULL && StackSize != 0) { | |
// | |
// Copy Stack to low memory stack | |
// | |
Stack16 -= StackSize / sizeof (UINT16); | |
CopyMem (Stack16, Stack, StackSize); | |
} | |
// | |
// Copy regs to low memory stack | |
// | |
Stack16 -= sizeof (EFI_IA32_REGISTER_SET) / sizeof (UINT16); | |
CopyMem (Stack16, Regs, sizeof (EFI_IA32_REGISTER_SET)); | |
// | |
// Provide low stack esp | |
// | |
TempData = ((UINTN) Stack16) - ((UINTN) IntThunk); | |
IntThunk->LowStack = *((UINT32 *) &TempData); | |
// | |
// The call to Legacy16 is a critical section to EFI | |
// | |
OriginalTpl = gBS->RaiseTPL (TPL_HIGH_LEVEL); | |
// | |
// Set Legacy16 state. 0x08, 0x70 is legacy 8259 vector bases. | |
// | |
Status = Private->Legacy8259->SetMode (Private->Legacy8259, Efi8259LegacyMode, NULL, NULL); | |
ASSERT_EFI_ERROR (Status); | |
// | |
// Call the real mode thunk code | |
// | |
Status = BiosIntCall ( | |
0x100, | |
(UINT16) IaSegment, | |
(UINT16) IaOffset, | |
(EFI_IA32_REGISTER_SET *) Stack16, | |
IntThunk, | |
IntThunk->LowStack | |
); | |
// | |
// Check for errors with the thunk | |
// | |
switch (Status) { | |
case THUNK_OK: | |
break; | |
case THUNK_ERR_A20_UNSUP: | |
case THUNK_ERR_A20_FAILED: | |
default: | |
// | |
// For all errors, set EFLAGS.CF (used by legacy BIOS to indicate error). | |
// | |
Regs->X.Flags.CF = 1; | |
break; | |
} | |
// | |
// Restore protected mode interrupt state | |
// | |
Status = Private->Legacy8259->SetMode (Private->Legacy8259, Efi8259ProtectedMode, NULL, NULL); | |
ASSERT_EFI_ERROR (Status); | |
// | |
// End critical section | |
// | |
gBS->RestoreTPL (OriginalTpl); | |
// | |
// Return the resulting registers | |
// | |
CopyMem (Regs, Stack16, sizeof (EFI_IA32_REGISTER_SET)); | |
Stack16 += sizeof (EFI_IA32_REGISTER_SET) / sizeof (UINT16); | |
if (Stack != NULL && StackSize != 0) { | |
// | |
// Copy low memory stack to Stack | |
// | |
CopyMem (Stack, Stack16, StackSize); | |
Stack16 += StackSize / sizeof (UINT16); | |
} | |
return (BOOLEAN) (Regs->X.Flags.CF != 0); | |
} |