Files
Ahmed Arif 229f7891a1 [FREELDR][HAL] Skip x86 BIOS emulator on UEFI boots (#8827)
Skip x86 BIOS emulator initialization on EFI boots by having FreeLdr populate EFI boot state in the loader block and making HAL consume it.

This keeps BIOS behavior unchanged while avoiding legacy BIOS/INT10 setup in the EFI path and preventing early EFI boot failures caused by entering the BIOS emulator path at all.
2026-04-02 13:37:43 +02:00

525 lines
14 KiB
C

/*
* PROJECT: ReactOS HAL
* LICENSE: GPL, See COPYING in the top level directory
* FILE: hal/halx86/amd64/x86bios.c
* PURPOSE:
* PROGRAMMERS: Timo Kreuzer ([email protected])
*/
/* INCLUDES ******************************************************************/
#include <hal.h>
//#define NDEBUG
#include <debug.h>
#include <fast486.h>
/* GLOBALS *******************************************************************/
/* This page serves as fallback for pages used by Mm */
PFN_NUMBER x86BiosFallbackPfn;
BOOLEAN x86BiosIsInitialized;
LONG x86BiosBufferIsAllocated = 0;
PUCHAR x86BiosMemoryMapping;
/* This the physical address of the bios buffer */
ULONG64 x86BiosBufferPhysical;
VOID
NTAPI
DbgDumpPage(PUCHAR MemBuffer, USHORT Segment)
{
ULONG x, y, Offset;
for (y = 0; y < 0x100; y++)
{
for (x = 0; x < 0x10; x++)
{
Offset = Segment * 16 + y * 16 + x;
DbgPrint("%02x ", MemBuffer[Offset]);
}
DbgPrint("\n");
}
}
VOID
NTAPI
HalInitializeBios(
_In_ ULONG Phase,
_In_ PLOADER_PARAMETER_BLOCK LoaderBlock)
{
PPFN_NUMBER PfnArray;
PFN_NUMBER Pfn, Last;
PMEMORY_ALLOCATION_DESCRIPTOR Descriptor;
PLIST_ENTRY ListEntry;
PMDL Mdl;
ULONG64 PhysicalAddress;
if (HalBootViaEfi)
return;
if (Phase == 0)
{
/* Allocate one page for a fallback mapping */
PhysicalAddress = HalpAllocPhysicalMemory(LoaderBlock,
0x100000,
1,
FALSE);
if (PhysicalAddress == 0)
{
ASSERT(FALSE);
}
x86BiosFallbackPfn = PhysicalAddress / PAGE_SIZE;
ASSERT(x86BiosFallbackPfn != 0);
/* Allocate a page for the buffer allocation */
x86BiosBufferPhysical = HalpAllocPhysicalMemory(LoaderBlock,
0x100000,
1,
FALSE);
if (x86BiosBufferPhysical == 0)
{
ASSERT(FALSE);
}
}
else
{
/* Allocate an MDL for 1MB */
Mdl = IoAllocateMdl(NULL, 0x100000, FALSE, FALSE, NULL);
if (!Mdl)
{
ASSERT(FALSE);
}
/* Get pointer to the pfn array */
PfnArray = MmGetMdlPfnArray(Mdl);
/* Fill the array with the fallback page */
for (Pfn = 0; Pfn < 0x100; Pfn++)
{
PfnArray[Pfn] = x86BiosFallbackPfn;
}
/* Loop the memory descriptors */
for (ListEntry = LoaderBlock->MemoryDescriptorListHead.Flink;
ListEntry != &LoaderBlock->MemoryDescriptorListHead;
ListEntry = ListEntry->Flink)
{
/* Get the memory descriptor */
Descriptor = CONTAINING_RECORD(ListEntry,
MEMORY_ALLOCATION_DESCRIPTOR,
ListEntry);
/* Check if the memory is in the low 1 MB range */
if (Descriptor->BasePage < 0x100)
{
/* Check if the memory type is firmware */
if ((Descriptor->MemoryType == LoaderFirmwarePermanent) ||
(Descriptor->MemoryType == LoaderSpecialMemory))
{
/* It's firmware, so map it! */
Last = min(Descriptor->BasePage + Descriptor->PageCount, 0x100);
for (Pfn = Descriptor->BasePage; Pfn < Last; Pfn++)
{
/* Set each physical page in the MDL */
PfnArray[Pfn] = Pfn;
}
}
}
}
/* Map this page proper, too */
Pfn = x86BiosBufferPhysical / PAGE_SIZE;
PfnArray[Pfn] = Pfn;
Mdl->MdlFlags = MDL_PAGES_LOCKED;
/* Map the MDL to system space */
x86BiosMemoryMapping = MmGetSystemAddressForMdlSafe(Mdl, HighPagePriority);
ASSERT(x86BiosMemoryMapping);
DPRINT1("*x86BiosMemoryMapping: %p, %p\n",
*(PVOID*)x86BiosMemoryMapping, *(PVOID*)(x86BiosMemoryMapping + 8));
//DbgDumpPage(x86BiosMemoryMapping, 0xc351);
x86BiosIsInitialized = TRUE;
}
}
NTSTATUS
NTAPI
x86BiosAllocateBuffer(
_Inout_ ULONG *Size,
_Out_ USHORT *Segment,
_Out_ USHORT *Offset)
{
/* Check if the system is initialized and the buffer is large enough */
if (!x86BiosIsInitialized || (*Size > PAGE_SIZE))
{
/* Something was wrong, fail! */
return STATUS_INSUFFICIENT_RESOURCES;
}
/* Check if the buffer is already allocated */
if (InterlockedBitTestAndSet(&x86BiosBufferIsAllocated, 0))
{
/* Buffer was already allocated, fail */
return STATUS_INSUFFICIENT_RESOURCES;
}
/* The buffer is sufficient, return hardcoded address and size */
*Size = PAGE_SIZE;
*Segment = x86BiosBufferPhysical / 16;
*Offset = 0;
return STATUS_SUCCESS;
}
NTSTATUS
NTAPI
x86BiosFreeBuffer(
_In_ USHORT Segment,
_In_ USHORT Offset)
{
/* Check if the system is initialized and if the address matches */
if (!x86BiosIsInitialized || (Segment != 0x2000) || (Offset != 0))
{
/* Something was wrong, fail */
return STATUS_INVALID_PARAMETER;
}
/* Check if the buffer was allocated */
if (!InterlockedBitTestAndReset(&x86BiosBufferIsAllocated, 0))
{
/* It was not, fail */
return STATUS_INVALID_PARAMETER;
}
/* Buffer is freed, nothing more to do */
return STATUS_SUCCESS;
}
NTSTATUS
NTAPI
x86BiosReadMemory(
_In_ USHORT Segment,
_In_ USHORT Offset,
_Out_writes_bytes_(Size) PVOID Buffer,
_In_ ULONG Size)
{
ULONG_PTR Address;
/* Calculate the physical address */
Address = (Segment << 4) + Offset;
/* Check if it's valid */
if (!x86BiosIsInitialized || ((Address + Size) > 0x100000))
{
/* Invalid */
return STATUS_INVALID_PARAMETER;
}
/* Copy the memory to the buffer */
RtlCopyMemory(Buffer, x86BiosMemoryMapping + Address, Size);
/* Return success */
return STATUS_SUCCESS;
}
NTSTATUS
NTAPI
x86BiosWriteMemory(
_In_ USHORT Segment,
_In_ USHORT Offset,
_In_reads_bytes_(Size) PVOID Buffer,
_In_ ULONG Size)
{
ULONG_PTR Address;
/* Calculate the physical address */
Address = (Segment << 4) + Offset;
/* Check if it's valid */
if (!x86BiosIsInitialized || ((Address + Size) > 0x100000))
{
/* Invalid */
return STATUS_INVALID_PARAMETER;
}
/* Copy the memory from the buffer */
RtlCopyMemory(x86BiosMemoryMapping + Address, Buffer, Size);
/* Return success */
return STATUS_SUCCESS;
}
static
VOID
FASTCALL
x86MemRead(
PFAST486_STATE State,
ULONG Address,
PVOID Buffer,
ULONG Size)
{
/* Validate the address range */
if (((ULONG64)Address + Size) < 0x100000)
{
RtlCopyMemory(Buffer, x86BiosMemoryMapping + Address, Size);
}
else
{
RtlFillMemory(Buffer, Size, 0xCC);
DPRINT1("x86MemRead: invalid read at 0x%lx (size 0x%lx)\n", Address, Size);
}
}
static
VOID
FASTCALL
x86MemWrite(
PFAST486_STATE State,
ULONG Address,
PVOID Buffer,
ULONG Size)
{
/* Validate the address range */
if (((ULONG64)Address + Size) < 0x100000)
{
RtlCopyMemory(x86BiosMemoryMapping + Address, Buffer, Size);
}
else
{
DPRINT1("x86MemWrite: invalid write at 0x%lx (size 0x%lx)\n", Address, Size);
}
}
static
BOOLEAN
ValidatePort(
USHORT Port,
UCHAR Size,
BOOLEAN IsWrite)
{
switch (Port)
{
// VGA: https://wiki.osdev.org/VGA_Hardware#Port_0x3C0
case 0x3C0: return (Size == 1) && IsWrite;
case 0x3C1: return (Size == 1) && !IsWrite;
case 0x3C2: return (Size == 1) && IsWrite;
case 0x3C4: return IsWrite;
case 0x3C5: return (Size <= 2);
case 0x3C7: return (Size == 1) && IsWrite;
case 0x3CC: return (Size == 1) && !IsWrite;
case 0x3CE: return IsWrite;
case 0x3CF: return (Size <= 2);
case 0x3D4: return IsWrite;
case 0x3D5: return (Size <= 2);
case 0x3C6: return (Size == 1);
case 0x3C8: return (Size == 1) && IsWrite;
case 0x3C9: return (Size == 1);
case 0x3DA: return (Size == 1) && !IsWrite;
// OVMF debug messages used by VBox / QEMU
// https://www.virtualbox.org/svn/vbox/trunk/src/VBox/Devices/EFI/Firmware/OvmfPkg/README
case 0x402: return (Size == 1) && IsWrite;
// BOCHS VBE: https://forum.osdev.org/viewtopic.php?f=1&t=14639
case 0x1CE: return (Size == 1) && IsWrite;
case 0x1CF: return (Size == 1);
// CHECKME!
case 0x3B6: return (Size <= 2);
}
/* Allow but report unknown ports, we trust the BIOS for now */
DPRINT1("Unknown port 0x%x, size %d, write %d\n", Port, Size, IsWrite);
return TRUE;
}
static
VOID
FASTCALL
x86IoRead(
PFAST486_STATE State,
USHORT Port,
PVOID Buffer,
ULONG DataCount,
UCHAR DataSize)
{
/* Validate the port */
if (!ValidatePort(Port, DataSize, FALSE))
{
DPRINT1("Invalid IO port read access (port: 0x%x, count: 0x%x)\n", Port, DataSize);
}
switch (DataSize)
{
case 1: READ_PORT_BUFFER_UCHAR((PUCHAR)(ULONG_PTR)Port, Buffer, DataCount); return;
case 2: READ_PORT_BUFFER_USHORT((PUSHORT)(ULONG_PTR)Port, Buffer, DataCount); return;
case 4: READ_PORT_BUFFER_ULONG((PULONG)(ULONG_PTR)Port, Buffer, DataCount); return;
}
}
static
VOID
FASTCALL
x86IoWrite(
PFAST486_STATE State,
USHORT Port,
PVOID Buffer,
ULONG DataCount,
UCHAR DataSize)
{
/* Validate the port */
if (!ValidatePort(Port, DataSize, TRUE))
{
DPRINT1("Invalid IO port write access (port: 0x%x, count: 0x%x)\n", Port, DataSize);
}
switch (DataSize)
{
case 1: WRITE_PORT_BUFFER_UCHAR((PUCHAR)(ULONG_PTR)Port, Buffer, DataCount); return;
case 2: WRITE_PORT_BUFFER_USHORT((PUSHORT)(ULONG_PTR)Port, Buffer, DataCount); return;
case 4: WRITE_PORT_BUFFER_ULONG((PULONG)(ULONG_PTR)Port, Buffer, DataCount); return;
}
}
static
VOID
FASTCALL
x86BOP(
PFAST486_STATE State,
UCHAR BopCode)
{
ASSERT(FALSE);
}
static
UCHAR
FASTCALL
x86IntAck (
PFAST486_STATE State)
{
ASSERT(FALSE);
return 0;
}
BOOLEAN
NTAPI
x86BiosCall(
_In_ ULONG InterruptNumber,
_Inout_ PX86_BIOS_REGISTERS Registers)
{
const ULONG StackBase = 0x2000;
FAST486_STATE EmulatorContext;
ULONG FlatIp;
PUCHAR InstructionPointer;
if (!x86BiosIsInitialized || (x86BiosMemoryMapping == NULL))
return FALSE;
/* Initialize the emulator context */
Fast486Initialize(&EmulatorContext,
x86MemRead,
x86MemWrite,
x86IoRead,
x86IoWrite,
x86BOP,
x86IntAck,
NULL, // FpuCallback,
NULL); // Tlb
/* Copy the GP registers */
EmulatorContext.GeneralRegs[FAST486_REG_EAX].Long = Registers->Eax;
EmulatorContext.GeneralRegs[FAST486_REG_EBX].Long = Registers->Ebx;
EmulatorContext.GeneralRegs[FAST486_REG_ECX].Long = Registers->Ecx;
EmulatorContext.GeneralRegs[FAST486_REG_EDX].Long = Registers->Edx;
EmulatorContext.GeneralRegs[FAST486_REG_ESI].Long = Registers->Esi;
EmulatorContext.GeneralRegs[FAST486_REG_EDI].Long = Registers->Edi;
/* Initialize segment registers */
Fast486SetSegment(&EmulatorContext, FAST486_REG_DS, Registers->SegDs);
Fast486SetSegment(&EmulatorContext, FAST486_REG_ES, Registers->SegEs);
/* Set Eflags */
EmulatorContext.Flags.Long = 0;
EmulatorContext.Flags.AlwaysSet = 1;
EmulatorContext.Flags.If = 1;
/* Set up the INT stub */
FlatIp = StackBase - 4;
InstructionPointer = x86BiosMemoryMapping + FlatIp;
InstructionPointer[0] = 0xCD; // INT instruction
InstructionPointer[1] = (UCHAR)InterruptNumber;
InstructionPointer[2] = 0x90; // NOP. We will stop at this address.
/* Set the stack pointer */
Fast486SetStack(&EmulatorContext, 0, StackBase - 8);
/* Start execution at the INT stub */
Fast486ExecuteAt(&EmulatorContext, 0x00, FlatIp);
while (TRUE)
{
/* Get the current flat IP */
FlatIp = (EmulatorContext.SegmentRegs[FAST486_REG_CS].Selector << 4) +
EmulatorContext.InstPtr.Long;
/* Make sure we haven't left the allowed memory range */
if (FlatIp >= 0x100000)
{
DPRINT1("x86BiosCall: invalid IP (0x%lx) during BIOS execution\n", FlatIp);
return FALSE;
}
/* Check if we returned from our int stub */
if (FlatIp == (StackBase - 2))
{
/* We are done! */
break;
}
/* Emulate one instruction */
Fast486StepInto(&EmulatorContext);
}
/* Copy the registers back */
Registers->Eax = EmulatorContext.GeneralRegs[FAST486_REG_EAX].Long;
Registers->Ebx = EmulatorContext.GeneralRegs[FAST486_REG_EBX].Long;
Registers->Ecx = EmulatorContext.GeneralRegs[FAST486_REG_ECX].Long;
Registers->Edx = EmulatorContext.GeneralRegs[FAST486_REG_EDX].Long;
Registers->Esi = EmulatorContext.GeneralRegs[FAST486_REG_ESI].Long;
Registers->Edi = EmulatorContext.GeneralRegs[FAST486_REG_EDI].Long;
Registers->SegDs = EmulatorContext.SegmentRegs[FAST486_REG_DS].Selector;
Registers->SegEs = EmulatorContext.SegmentRegs[FAST486_REG_ES].Selector;
return TRUE;
}
#ifdef _M_AMD64
BOOLEAN
NTAPI
HalpBiosDisplayReset(VOID)
{
X86_BIOS_REGISTERS Registers;
ULONG OldEflags;
BOOLEAN Success;
/* Save flags and disable interrupts */
OldEflags = __readeflags();
_disable();
/* Call INT 0x10, AH = 0 (Set video mode), AL = 0x12 (640x480x16 VGA) */
Registers.Eax = 0x0012;
Success = x86BiosCall(0x10, &Registers);
/* Restore previous flags */
__writeeflags(OldEflags);
return Success;
}
#endif // _M_AMD64