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 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 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 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 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 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 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